importProfile.spec.tsx 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. import {EventedProfile} from 'sentry/utils/profiling/profile/eventedProfile';
  2. import {
  3. importProfile,
  4. parseDroppedProfile,
  5. } from 'sentry/utils/profiling/profile/importProfile';
  6. import {JSSelfProfile} from 'sentry/utils/profiling/profile/jsSelfProfile';
  7. import {SampledProfile} from 'sentry/utils/profiling/profile/sampledProfile';
  8. import {SentrySampledProfile} from './sentrySampledProfile';
  9. import {makeSentrySampledProfile} from './sentrySampledProfile.spec';
  10. describe('importProfile', () => {
  11. it('imports evented profile', () => {
  12. const eventedProfile: Profiling.EventedProfile = {
  13. name: 'profile',
  14. startValue: 0,
  15. endValue: 1000,
  16. threadID: 0,
  17. unit: 'milliseconds',
  18. type: 'evented',
  19. events: [],
  20. };
  21. const imported = importProfile(
  22. {
  23. activeProfileIndex: 0,
  24. profileID: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  25. profiles: [eventedProfile],
  26. projectID: 1,
  27. shared: {
  28. frames: [],
  29. },
  30. metadata: {} as Profiling.Schema['metadata'],
  31. },
  32. '',
  33. 'flamechart'
  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. 'flamechart'
  61. );
  62. expect(imported.profiles[0]).toBeInstanceOf(SampledProfile);
  63. });
  64. it('imports JS self profile from schema', () => {
  65. const jsSelfProfile: JSSelfProfiling.Trace = {
  66. resources: ['app.js', 'vendor.js'],
  67. frames: [{name: 'ReactDOM.render', line: 1, column: 1, resourceId: 0}],
  68. samples: [
  69. {
  70. timestamp: 0,
  71. stackId: 0,
  72. },
  73. {
  74. timestamp: 1000,
  75. stackId: 0,
  76. },
  77. ],
  78. stacks: [
  79. {
  80. frameId: 0,
  81. },
  82. ],
  83. };
  84. const imported = importProfile(
  85. {
  86. activeProfileIndex: 0,
  87. profileID: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  88. profiles: [jsSelfProfile],
  89. projectID: 1,
  90. metadata: {} as Profiling.Schema['metadata'],
  91. shared: {
  92. frames: [],
  93. },
  94. },
  95. '',
  96. 'flamechart'
  97. );
  98. expect(imported.profiles[0]).toBeInstanceOf(JSSelfProfile);
  99. });
  100. it('imports JS self profile from raw Profiling output', () => {
  101. const jsSelfProfile: JSSelfProfiling.Trace = {
  102. resources: ['app.js', 'vendor.js'],
  103. frames: [{name: 'ReactDOM.render', line: 1, column: 1, resourceId: 0}],
  104. samples: [
  105. {
  106. timestamp: 0,
  107. stackId: 0,
  108. },
  109. {
  110. timestamp: 1000,
  111. stackId: 0,
  112. },
  113. ],
  114. stacks: [
  115. {
  116. frameId: 0,
  117. },
  118. ],
  119. };
  120. const imported = importProfile(jsSelfProfile, 'profile', 'flamechart');
  121. expect(imported.profiles[0]).toBeInstanceOf(JSSelfProfile);
  122. });
  123. it('imports sentry sampled profile', () => {
  124. const sentrySampledProfile = makeSentrySampledProfile();
  125. const imported = importProfile(sentrySampledProfile, 'profile', 'flamegraph');
  126. expect(imported.profiles[0]).toBeInstanceOf(SentrySampledProfile);
  127. });
  128. it('throws on unrecognized profile type', () => {
  129. expect(() =>
  130. importProfile(
  131. // @ts-expect-error
  132. {name: 'profile', activeProfileIndex: 0, profiles: [{type: 'unrecognized'}]},
  133. '',
  134. 'flamechart'
  135. )
  136. ).toThrow();
  137. });
  138. });
  139. describe('parseDroppedProfile', () => {
  140. beforeEach(() => {
  141. jest.restoreAllMocks();
  142. });
  143. it('throws if file has no string contents', async () => {
  144. // @ts-expect-error we are just setting null on the file, we are not actually reading it because our event is mocked
  145. const file = new File([null], 'test.tsx');
  146. const reader = new FileReader();
  147. jest.spyOn(window, 'FileReader').mockImplementation(() => reader);
  148. jest.spyOn(reader, 'readAsText').mockImplementation(() => {
  149. const loadEvent = new CustomEvent('load', {
  150. detail: {target: {result: null}},
  151. });
  152. reader.dispatchEvent(loadEvent);
  153. });
  154. await expect(parseDroppedProfile(file)).rejects.toEqual(
  155. 'Failed to read string contents of input file'
  156. );
  157. });
  158. it('throws if FileReader errors', async () => {
  159. const file = new File(['{json: true}'], 'test.tsx');
  160. const reader = new FileReader();
  161. jest.spyOn(window, 'FileReader').mockImplementation(() => reader);
  162. jest.spyOn(reader, 'readAsText').mockImplementation(() => {
  163. const loadEvent = new CustomEvent('error', {
  164. detail: {target: {result: null}},
  165. });
  166. reader.dispatchEvent(loadEvent);
  167. });
  168. await expect(parseDroppedProfile(file)).rejects.toEqual(
  169. 'Failed to read string contents of input file'
  170. );
  171. });
  172. it('throws if contents are not valid JSON', async () => {
  173. const file = new File(['{"json": true'], 'test.tsx');
  174. await expect(parseDroppedProfile(file)).rejects.toBeInstanceOf(Error);
  175. });
  176. it('imports dropped schema file', async () => {
  177. const schema: Readonly<Profiling.Schema> = {
  178. activeProfileIndex: 0,
  179. profileID: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  180. profiles: [
  181. {
  182. name: 'profile',
  183. startValue: 0,
  184. endValue: 1000,
  185. threadID: 0,
  186. unit: 'milliseconds',
  187. type: 'sampled',
  188. weights: [],
  189. samples: [],
  190. },
  191. ],
  192. projectID: 1,
  193. shared: {
  194. frames: [],
  195. },
  196. metadata: {} as Profiling.Schema['metadata'],
  197. };
  198. const file = new File([JSON.stringify(schema)], 'test.tsx');
  199. const imported = importProfile(
  200. await parseDroppedProfile(file),
  201. file.name,
  202. 'flamechart'
  203. );
  204. expect(imported.profiles[0]).toBeInstanceOf(SampledProfile);
  205. });
  206. it('imports dropped JS self profile', async () => {
  207. const jsSelfProfile: JSSelfProfiling.Trace = {
  208. resources: ['app.js', 'vendor.js'],
  209. frames: [{name: 'ReactDOM.render', line: 1, column: 1, resourceId: 0}],
  210. samples: [
  211. {
  212. timestamp: 0,
  213. stackId: 0,
  214. },
  215. {
  216. timestamp: 1000,
  217. stackId: 0,
  218. },
  219. ],
  220. stacks: [
  221. {
  222. frameId: 0,
  223. },
  224. ],
  225. };
  226. const file = new File([JSON.stringify(jsSelfProfile)], 'test.tsx');
  227. const imported = importProfile(
  228. await parseDroppedProfile(file),
  229. file.name,
  230. 'flamechart'
  231. );
  232. expect(imported.profiles[0]).toBeInstanceOf(JSSelfProfile);
  233. });
  234. });