jest.config.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. /* eslint-env node */
  2. /* eslint import/no-nodejs-modules:0 */
  3. import path from 'path';
  4. import process from 'process';
  5. import type {Config} from '@jest/types';
  6. import babelConfig from './babel.config';
  7. const {
  8. CI,
  9. JEST_TESTS,
  10. JEST_TEST_BALANCER,
  11. CI_NODE_TOTAL,
  12. CI_NODE_INDEX,
  13. GITHUB_PR_SHA,
  14. GITHUB_PR_REF,
  15. GITHUB_RUN_ID,
  16. GITHUB_RUN_ATTEMPT,
  17. } = process.env;
  18. /**
  19. * In CI we may need to shard our jest tests so that we can parellize the test runs
  20. *
  21. * `JEST_TESTS` is a list of all tests that will run, captured by `jest --listTests`
  22. * Then we split up the tests based on the total number of CI instances that will
  23. * be running the tests.
  24. */
  25. let testMatch: string[] | undefined;
  26. const BALANCE_RESULTS_PATH = path.resolve(
  27. __dirname,
  28. 'tests',
  29. 'js',
  30. 'test-balancer',
  31. 'jest-balance.json'
  32. );
  33. /**
  34. * Given a Map of <testName, testRunTime> and a number of total groups, split the
  35. * tests into n groups whose total test run times should be roughly equal
  36. *
  37. * The source results should be sorted with the slowest tests first. We insert
  38. * the test into the smallest group on each iteration. This isn't perfect, but
  39. * should be good enough.
  40. *
  41. * Returns a map of <testName, groupIndex>
  42. */
  43. function balancer(
  44. allTests: string[],
  45. source: Record<string, number>,
  46. numberGroups: number
  47. ) {
  48. const results = new Map<string, number>();
  49. const totalRunTimes = Array(numberGroups).fill(0);
  50. /**
  51. * Find the index of the smallest group (totalRunTimes)
  52. */
  53. function findSmallestGroup() {
  54. let index = 0;
  55. let smallestRunTime = null;
  56. for (let i = 0; i < totalRunTimes.length; i++) {
  57. const runTime = totalRunTimes[i];
  58. if (!smallestRunTime || runTime <= smallestRunTime) {
  59. smallestRunTime = totalRunTimes[i];
  60. index = i;
  61. }
  62. if (runTime === 0) {
  63. break;
  64. }
  65. }
  66. return index;
  67. }
  68. /**
  69. * We may not have a duration for all tests (e.g. a test that was just added)
  70. * as the `source` needs to be generated
  71. */
  72. for (const test of allTests) {
  73. const index = findSmallestGroup();
  74. results.set(test, index);
  75. if (source[test] !== undefined) {
  76. totalRunTimes[index] = totalRunTimes[index] + source[test];
  77. }
  78. }
  79. return results;
  80. }
  81. if (
  82. JEST_TESTS &&
  83. typeof CI_NODE_TOTAL !== 'undefined' &&
  84. typeof CI_NODE_INDEX !== 'undefined'
  85. ) {
  86. let balance: null | Record<string, number> = null;
  87. try {
  88. balance = require(BALANCE_RESULTS_PATH);
  89. } catch (err) {
  90. // Just ignore if balance results doesn't exist
  91. }
  92. // Taken from https://github.com/facebook/jest/issues/6270#issue-326653779
  93. const envTestList = JSON.parse(JEST_TESTS).map(file =>
  94. file.replace(__dirname, '')
  95. ) as string[];
  96. const tests = envTestList.sort((a, b) => b.localeCompare(a));
  97. const nodeTotal = Number(CI_NODE_TOTAL);
  98. const nodeIndex = Number(CI_NODE_INDEX);
  99. if (balance) {
  100. const results = balancer(envTestList, balance, nodeTotal);
  101. testMatch = [
  102. // First, we only want the tests that we have test durations for and belong
  103. // to the current node's index
  104. ...Object.entries(Object.fromEntries(results))
  105. .filter(([, index]) => index === nodeIndex)
  106. .map(([test]) => `${path.join(__dirname, test)}`),
  107. ];
  108. } else {
  109. const length = tests.length;
  110. const size = Math.floor(length / nodeTotal);
  111. const remainder = length % nodeTotal;
  112. const offset = Math.min(nodeIndex, remainder) + nodeIndex * size;
  113. const chunk = size + (nodeIndex < remainder ? 1 : 0);
  114. testMatch = tests.slice(offset, offset + chunk);
  115. }
  116. }
  117. /**
  118. * For performance we don't want to try and compile everything in the
  119. * node_modules, but some packages which use ES6 syntax only NEED to be
  120. * transformed.
  121. */
  122. const ESM_NODE_MODULES = ['copy-text-to-clipboard'];
  123. const config: Config.InitialOptions = {
  124. verbose: false,
  125. collectCoverageFrom: [
  126. 'tests/js/spec/**/*.{js,jsx,tsx}',
  127. 'static/app/**/*.{js,jsx,ts,tsx}',
  128. ],
  129. coverageReporters: ['html', 'cobertura'],
  130. coverageDirectory: '.artifacts/coverage',
  131. moduleNameMapper: {
  132. '^sentry/(.*)': '<rootDir>/static/app/$1',
  133. '^sentry-test/(.*)': '<rootDir>/tests/js/sentry-test/$1',
  134. '^sentry-locale/(.*)': '<rootDir>/src/sentry/locale/$1',
  135. '\\.(css|less|png|jpg|mp4)$': '<rootDir>/tests/js/sentry-test/importStyleMock.js',
  136. '\\.(svg)$': '<rootDir>/tests/js/sentry-test/svgMock.js',
  137. 'integration-docs-platforms': '<rootDir>/fixtures/integration-docs/_platforms.json',
  138. // Disable echarts in test, since they're very slow and take time to
  139. // transform
  140. '^echarts/(.*)': '<rootDir>/tests/js/sentry-test/echartsMock.js',
  141. '^zrender/(.*)': '<rootDir>/tests/js/sentry-test/echartsMock.js',
  142. },
  143. setupFiles: [
  144. '<rootDir>/static/app/utils/silence-react-unsafe-warnings.ts',
  145. '<rootDir>/tests/js/throw-on-react-error.js',
  146. 'jest-canvas-mock',
  147. ],
  148. setupFilesAfterEnv: [
  149. '<rootDir>/tests/js/setup.ts',
  150. '<rootDir>/tests/js/setupFramework.ts',
  151. '@testing-library/jest-dom/extend-expect',
  152. ],
  153. testMatch: testMatch || ['<rootDir>/tests/js/**/*(*.)@(spec|test).(js|ts)?(x)'],
  154. testPathIgnorePatterns: ['<rootDir>/tests/sentry/lang/javascript/'],
  155. unmockedModulePathPatterns: [
  156. '<rootDir>/node_modules/react',
  157. '<rootDir>/node_modules/reflux',
  158. ],
  159. transform: {
  160. '^.+\\.jsx?$': ['babel-jest', babelConfig as any],
  161. '^.+\\.tsx?$': ['babel-jest', babelConfig as any],
  162. '^.+\\.pegjs?$': '<rootDir>/tests/js/jest-pegjs-transform.js',
  163. },
  164. transformIgnorePatterns: [`/node_modules/(?!${ESM_NODE_MODULES.join('|')})`],
  165. moduleFileExtensions: ['js', 'ts', 'jsx', 'tsx'],
  166. globals: {},
  167. reporters: [
  168. 'default',
  169. [
  170. 'jest-junit',
  171. {
  172. outputDirectory: '.artifacts',
  173. outputName: 'jest.junit.xml',
  174. },
  175. ],
  176. [
  177. '<rootDir>/tests/js/test-balancer',
  178. {
  179. enabled: !!JEST_TEST_BALANCER,
  180. resultsPath: BALANCE_RESULTS_PATH,
  181. },
  182. ],
  183. ],
  184. testEnvironment: '<rootDir>/tests/js/instrumentedEnv',
  185. testEnvironmentOptions: {
  186. sentryConfig: {
  187. init: {
  188. // jest project under Sentry organization (dev productivity team)
  189. dsn: 'https://3fe1dce93e3a4267979ebad67f3de327@sentry.io/4857230',
  190. environment: !!CI ? 'ci' : 'local',
  191. tracesSampleRate: 1.0,
  192. },
  193. transactionOptions: {
  194. tags: {
  195. branch: GITHUB_PR_REF,
  196. commit: GITHUB_PR_SHA,
  197. github_run_attempt: GITHUB_RUN_ATTEMPT,
  198. github_actions_run: `https://github.com/getsentry/sentry/actions/runs/${GITHUB_RUN_ID}`,
  199. },
  200. },
  201. },
  202. output: path.resolve(__dirname, '.artifacts', 'visual-snapshots', 'jest'),
  203. },
  204. };
  205. export default config;