importProfile.spec.tsx 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. import {ChromeTraceProfile} from 'sentry/utils/profiling/profile/chromeTraceProfile';
  2. import {EventedProfile} from 'sentry/utils/profiling/profile/eventedProfile';
  3. import {
  4. importDroppedProfile,
  5. importProfile,
  6. } from 'sentry/utils/profiling/profile/importProfile';
  7. import {JSSelfProfile} from 'sentry/utils/profiling/profile/jsSelfProfile';
  8. import {SampledProfile} from 'sentry/utils/profiling/profile/sampledProfile';
  9. import {SentrySampledProfile} from './sentrySampledProfile';
  10. import {makeSentrySampledProfile} from './sentrySampledProfile.spec';
  11. describe('importProfile', () => {
  12. it('imports evented profile', () => {
  13. const eventedProfile: Profiling.EventedProfile = {
  14. name: 'profile',
  15. startValue: 0,
  16. endValue: 1000,
  17. threadID: 0,
  18. unit: 'milliseconds',
  19. type: 'evented',
  20. events: [],
  21. };
  22. const imported = importProfile(
  23. {
  24. activeProfileIndex: 0,
  25. profileID: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  26. profiles: [eventedProfile],
  27. projectID: 1,
  28. shared: {
  29. frames: [],
  30. },
  31. metadata: {} as Profiling.Schema['metadata'],
  32. },
  33. ''
  34. );
  35. expect(imported.profiles[0]).toBeInstanceOf(EventedProfile);
  36. });
  37. it('imports sampled profile', () => {
  38. const sampledProfile: Profiling.SampledProfile = {
  39. name: 'profile',
  40. startValue: 0,
  41. endValue: 1000,
  42. threadID: 0,
  43. unit: 'milliseconds',
  44. type: 'sampled',
  45. weights: [],
  46. samples: [],
  47. };
  48. const imported = importProfile(
  49. {
  50. activeProfileIndex: 0,
  51. profileID: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  52. profiles: [sampledProfile],
  53. projectID: 1,
  54. shared: {
  55. frames: [],
  56. },
  57. metadata: {} as Profiling.Schema['metadata'],
  58. },
  59. ''
  60. );
  61. expect(imported.profiles[0]).toBeInstanceOf(SampledProfile);
  62. });
  63. it('imports typescript profile', () => {
  64. const typescriptProfile: ChromeTrace.ArrayFormat = [
  65. {
  66. ph: 'B',
  67. ts: 1000,
  68. cat: 'program',
  69. pid: 0,
  70. tid: 0,
  71. name: 'createProgram',
  72. args: {configFilePath: '/Users/jonasbadalic/Work/sentry/tsconfig.json'},
  73. },
  74. {
  75. ph: 'E',
  76. ts: 2000,
  77. cat: 'program',
  78. pid: 0,
  79. tid: 0,
  80. name: 'createProgram',
  81. args: {configFilePath: '/Users/jonasbadalic/Work/sentry/tsconfig.json'},
  82. },
  83. ];
  84. const imported = importProfile(typescriptProfile, '');
  85. expect(imported.profiles[0]).toBeInstanceOf(ChromeTraceProfile);
  86. });
  87. it('imports a nodejs profile', () => {
  88. const nodeProfile: Profiling.NodeProfile = {
  89. name: 'profile',
  90. startValue: 0,
  91. endValue: 1000,
  92. threadID: 0,
  93. unit: 'milliseconds',
  94. type: 'sampled',
  95. weights: [],
  96. samples: [],
  97. frames: [],
  98. };
  99. const imported = importProfile([nodeProfile, {}], '');
  100. expect(imported.profiles[0]).toBeInstanceOf(SampledProfile);
  101. });
  102. it('imports JS self profile from schema', () => {
  103. const jsSelfProfile: JSSelfProfiling.Trace = {
  104. resources: ['app.js', 'vendor.js'],
  105. frames: [{name: 'ReactDOM.render', line: 1, column: 1, resourceId: 0}],
  106. samples: [
  107. {
  108. timestamp: 0,
  109. },
  110. {
  111. timestamp: 1000,
  112. stackId: 0,
  113. },
  114. ],
  115. stacks: [
  116. {
  117. frameId: 0,
  118. },
  119. ],
  120. };
  121. const imported = importProfile(
  122. {
  123. activeProfileIndex: 0,
  124. profileID: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  125. profiles: [jsSelfProfile],
  126. projectID: 1,
  127. metadata: {} as Profiling.Schema['metadata'],
  128. shared: {
  129. frames: [],
  130. },
  131. },
  132. ''
  133. );
  134. expect(imported.profiles[0]).toBeInstanceOf(JSSelfProfile);
  135. });
  136. it('imports JS self profile from raw Profiling output', () => {
  137. const jsSelfProfile: JSSelfProfiling.Trace = {
  138. resources: ['app.js', 'vendor.js'],
  139. frames: [{name: 'ReactDOM.render', line: 1, column: 1, resourceId: 0}],
  140. samples: [
  141. {
  142. timestamp: 0,
  143. },
  144. {
  145. timestamp: 1000,
  146. stackId: 0,
  147. },
  148. ],
  149. stacks: [
  150. {
  151. frameId: 0,
  152. },
  153. ],
  154. };
  155. const imported = importProfile(jsSelfProfile, 'profile');
  156. expect(imported.profiles[0]).toBeInstanceOf(JSSelfProfile);
  157. });
  158. it('imports sentry sampled profile', () => {
  159. const sentrySampledProfile = makeSentrySampledProfile();
  160. const imported = importProfile(sentrySampledProfile, 'profile');
  161. expect(imported.profiles[0]).toBeInstanceOf(SentrySampledProfile);
  162. });
  163. it('throws on unrecognized profile type', () => {
  164. expect(() =>
  165. importProfile(
  166. // @ts-ignore
  167. {name: 'profile', activeProfileIndex: 0, profiles: [{type: 'unrecognized'}]},
  168. ''
  169. )
  170. ).toThrow();
  171. });
  172. });
  173. describe('importDroppedProfile', () => {
  174. beforeEach(() => {
  175. jest.restoreAllMocks();
  176. });
  177. it('throws if file has no string contents', async () => {
  178. // @ts-ignore we are just setting null on the file, we are not actually reading it because our event is mocked
  179. const file = new File([null], 'test.tsx');
  180. const reader = new FileReader();
  181. jest.spyOn(window, 'FileReader').mockImplementation(() => reader);
  182. jest.spyOn(reader, 'readAsText').mockImplementation(() => {
  183. const loadEvent = new CustomEvent('load', {
  184. detail: {target: {result: null}},
  185. });
  186. reader.dispatchEvent(loadEvent);
  187. });
  188. await expect(importDroppedProfile(file)).rejects.toEqual(
  189. 'Failed to read string contents of input file'
  190. );
  191. });
  192. it('throws if FileReader errors', async () => {
  193. const file = new File(['{json: true}'], 'test.tsx');
  194. const reader = new FileReader();
  195. jest.spyOn(window, 'FileReader').mockImplementation(() => reader);
  196. jest.spyOn(reader, 'readAsText').mockImplementation(() => {
  197. const loadEvent = new CustomEvent('error', {
  198. detail: {target: {result: null}},
  199. });
  200. reader.dispatchEvent(loadEvent);
  201. });
  202. await expect(importDroppedProfile(file)).rejects.toEqual(
  203. 'Failed to read string contents of input file'
  204. );
  205. });
  206. it('throws if contents are not valid JSON', async () => {
  207. const file = new File(['{"json": true'], 'test.tsx');
  208. await expect(importDroppedProfile(file)).rejects.toBeInstanceOf(Error);
  209. });
  210. it('imports dropped schema file', async () => {
  211. const schema: Profiling.Schema = {
  212. activeProfileIndex: 0,
  213. profileID: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  214. profiles: [
  215. {
  216. name: 'profile',
  217. startValue: 0,
  218. endValue: 1000,
  219. threadID: 0,
  220. unit: 'milliseconds',
  221. type: 'sampled',
  222. weights: [],
  223. samples: [],
  224. },
  225. ],
  226. projectID: 1,
  227. shared: {
  228. frames: [],
  229. },
  230. metadata: {} as Profiling.Schema['metadata'],
  231. };
  232. const file = new File([JSON.stringify(schema)], 'test.tsx');
  233. const imported = await importDroppedProfile(file);
  234. expect(imported.profiles[0]).toBeInstanceOf(SampledProfile);
  235. });
  236. it('imports dropped typescript profile', async () => {
  237. const typescriptProfile: ChromeTrace.ArrayFormat = [
  238. {
  239. ph: 'B',
  240. ts: 1000,
  241. cat: 'program',
  242. pid: 0,
  243. tid: 0,
  244. name: 'createProgram',
  245. args: {configFilePath: '/Users/jonasbadalic/Work/sentry/tsconfig.json'},
  246. },
  247. {
  248. ph: 'E',
  249. ts: 2000,
  250. cat: 'program',
  251. pid: 0,
  252. tid: 0,
  253. name: 'createProgram',
  254. args: {configFilePath: '/Users/jonasbadalic/Work/sentry/tsconfig.json'},
  255. },
  256. ];
  257. const file = new File([JSON.stringify(typescriptProfile)], 'test.tsx');
  258. const imported = await importDroppedProfile(file);
  259. expect(imported.profiles[0]).toBeInstanceOf(ChromeTraceProfile);
  260. });
  261. it('imports dropped JS self profile', async () => {
  262. const jsSelfProfile: JSSelfProfiling.Trace = {
  263. resources: ['app.js', 'vendor.js'],
  264. frames: [{name: 'ReactDOM.render', line: 1, column: 1, resourceId: 0}],
  265. samples: [
  266. {
  267. timestamp: 0,
  268. },
  269. {
  270. timestamp: 1000,
  271. stackId: 0,
  272. },
  273. ],
  274. stacks: [
  275. {
  276. frameId: 0,
  277. },
  278. ],
  279. };
  280. const file = new File([JSON.stringify(jsSelfProfile)], 'test.tsx');
  281. const imported = await importDroppedProfile(file);
  282. expect(imported.profiles[0]).toBeInstanceOf(JSSelfProfile);
  283. });
  284. });