jest.config.ts 6.1 KB

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