diff --git a/.circleci/config.base.yml b/.circleci/config.base.yml index f1da5a4f..4762bd86 100644 --- a/.circleci/config.base.yml +++ b/.circleci/config.base.yml @@ -303,7 +303,7 @@ jobs: yarn clean-e2e-resources no_output_timeout: 20m - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports + path: ~/repo/packages/amplify-codegen-e2e-tests/amplify-e2e-reports working_directory: ~/repo verify-api-extract: diff --git a/.circleci/config.yml b/.circleci/config.yml index f6e9a51d..e3a5af6b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -306,7 +306,7 @@ jobs: yarn clean-e2e-resources no_output_timeout: 20m - store_artifacts: - path: ~/repo/packages/amplify-e2e-tests/amplify-e2e-reports + path: ~/repo/packages/amplify-codegen-e2e-tests/amplify-e2e-reports verify-api-extract: docker: *ref_4 working_directory: ~/repo diff --git a/.codebuild/cleanup_workflow.yml b/.codebuild/cleanup_workflow.yml index 4d7fdfe7..e5ca317d 100644 --- a/.codebuild/cleanup_workflow.yml +++ b/.codebuild/cleanup_workflow.yml @@ -9,4 +9,4 @@ phases: build: commands: - yarn production-build - - cd packages/amplify-e2e-tests && yarn clean-cb-e2e-resources \ No newline at end of file + - cd packages/amplify-codegen-e2e-tests && yarn clean-cb-e2e-resources \ No newline at end of file diff --git a/.codebuild/e2e_workflow.yml b/.codebuild/e2e_workflow.yml index 74d4e57b..3d06ed2e 100644 --- a/.codebuild/e2e_workflow.yml +++ b/.codebuild/e2e_workflow.yml @@ -1,8 +1,8 @@ +# auto generated file. DO NOT EDIT manually version: 0.2 env: shell: bash compute-type: BUILD_GENERAL1_MEDIUM - batch: fast-fail: false build-graph: @@ -28,3 +28,85 @@ batch: compute-type: BUILD_GENERAL1_MEDIUM depend-on: - build_linux + - identifier: >- + add_codegen_ios_configure_codegen_android_configure_codegen_js_graphql_codegen_android + buildspec: .codebuild/run_e2e_tests.yml + env: + compute-type: BUILD_GENERAL1_MEDIUM + variables: + TEST_SUITE: >- + src/__tests__/add-codegen-ios.test.ts|src/__tests__/configure-codegen-android.test.ts|src/__tests__/configure-codegen-js.test.ts|src/__tests__/graphql-codegen-android.test.ts + CLI_REGION: us-east-1 + depend-on: + - publish_to_local_registry + - identifier: >- + graphql_codegen_js_remove_codegen_android_remove_codegen_ios_add_codegen_android + buildspec: .codebuild/run_e2e_tests.yml + env: + compute-type: BUILD_GENERAL1_MEDIUM + variables: + TEST_SUITE: >- + src/__tests__/graphql-codegen-js.test.ts|src/__tests__/remove-codegen-android.test.ts|src/__tests__/remove-codegen-ios.test.ts|src/__tests__/add-codegen-android.test.ts + CLI_REGION: us-east-2 + depend-on: + - publish_to_local_registry + - identifier: >- + configure_codegen_ios_datastore_modelgen_android_datastore_modelgen_js_feature_flags + buildspec: .codebuild/run_e2e_tests.yml + env: + compute-type: BUILD_GENERAL1_MEDIUM + variables: + TEST_SUITE: >- + src/__tests__/configure-codegen-ios.test.ts|src/__tests__/datastore-modelgen-android.test.ts|src/__tests__/datastore-modelgen-js.test.ts|src/__tests__/feature-flags.test.ts + CLI_REGION: us-west-2 + depend-on: + - publish_to_local_registry + - identifier: >- + graphql_codegen_ios_add_codegen_js_datastore_modelgen_ios_remove_codegen_js + buildspec: .codebuild/run_e2e_tests.yml + env: + compute-type: BUILD_GENERAL1_MEDIUM + variables: + TEST_SUITE: >- + src/__tests__/graphql-codegen-ios.test.ts|src/__tests__/add-codegen-js.test.ts|src/__tests__/datastore-modelgen-ios.test.ts|src/__tests__/remove-codegen-js.test.ts + CLI_REGION: eu-west-2 + depend-on: + - publish_to_local_registry + - identifier: >- + datastore_modelgen_flutter_env_codegen_model_introspection_codegen_pull_codegen + buildspec: .codebuild/run_e2e_tests.yml + env: + compute-type: BUILD_GENERAL1_MEDIUM + variables: + TEST_SUITE: >- + src/__tests__/datastore-modelgen-flutter.test.ts|src/__tests__/env-codegen.test.ts|src/__tests__/model-introspection-codegen.test.ts|src/__tests__/pull-codegen.test.ts + CLI_REGION: eu-central-1 + depend-on: + - publish_to_local_registry + - identifier: >- + push_codegen_ios_push_codegen_android_graphql_documents_generator_push_codegen_js + buildspec: .codebuild/run_e2e_tests.yml + env: + compute-type: BUILD_GENERAL1_MEDIUM + variables: + TEST_SUITE: >- + src/__tests__/push-codegen-ios.test.ts|src/__tests__/push-codegen-android.test.ts|src/__tests__/graphql-documents-generator.test.ts|src/__tests__/push-codegen-js.test.ts + CLI_REGION: ap-northeast-1 + depend-on: + - publish_to_local_registry + - identifier: build_app_ts + buildspec: .codebuild/run_e2e_tests.yml + env: + compute-type: BUILD_GENERAL1_MEDIUM + variables: + TEST_SUITE: src/__tests__/build-app-ts.test.ts + CLI_REGION: ap-southeast-1 + depend-on: + - publish_to_local_registry + - identifier: cleanup_e2e_resources + buildspec: .codebuild/cleanup_e2e_resources.yml + env: + compute-type: BUILD_GENERAL1_SMALL + depend-on: + - >- + add_codegen_ios_configure_codegen_android_configure_codegen_js_graphql_codegen_android diff --git a/.codebuild/scripts/artifact-storage-path-allow-list-codebuild.ts b/.codebuild/scripts/artifact-storage-path-allow-list-codebuild.ts index f89fa334..60824994 100644 --- a/.codebuild/scripts/artifact-storage-path-allow-list-codebuild.ts +++ b/.codebuild/scripts/artifact-storage-path-allow-list-codebuild.ts @@ -9,10 +9,10 @@ * artifacts: files: - - $CODEBUILD_SRC_DIR/packages/amplify-e2e-tests/amplify-e2e-reports/* + - $CODEBUILD_SRC_DIR/packages/amplify-codegen-e2e-tests/amplify-e2e-reports/* * * From the above job, 'path' includes the following: - * $CODEBUILD_SRC_DIR/packages/amplify-e2e-tests/amplify-e2e-reports + * $CODEBUILD_SRC_DIR/packages/amplify-codegen-e2e-tests/amplify-e2e-reports * * Those paths must be included in this list. * @@ -27,6 +27,6 @@ * will automatically normalize these paths for Windows if it detects it. */ export const ARTIFACT_STORAGE_PATH_ALLOW_LIST_CODEBUILD = [ - '$CODEBUILD_SRC_DIR/packages/amplify-e2e-tests/', - '$CODEBUILD_SRC_DIR/packages/amplify-e2e-tests/amplify-e2e-reports' + '$CODEBUILD_SRC_DIR/packages/amplify-codegen-e2e-tests/', + '$CODEBUILD_SRC_DIR/packages/amplify-codegen-e2e-tests/amplify-e2e-reports' ]; \ No newline at end of file diff --git a/.codebuild/scripts/local_publish_helpers.sh b/.codebuild/scripts/local_publish_helpers.sh index c96de99e..a775ca6d 100644 --- a/.codebuild/scripts/local_publish_helpers.sh +++ b/.codebuild/scripts/local_publish_helpers.sh @@ -44,7 +44,9 @@ function unsetSudoNpmRegistryUrl { function changeNpmGlobalPath { mkdir -p ~/.npm-global - npm config set prefix '~/.npm-global' + if [ -z $SKIP_SET_NPM_PREFIX ]; then + npm config set prefix '~/.npm-global' + fi export PATH=~/.npm-global/bin:$PATH } diff --git a/.gitignore b/.gitignore index 5008007a..5b8f7018 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,5 @@ test.out.log *.tsbuildinfo package-lock.json .idea -scripts/.env \ No newline at end of file +scripts/.env +scripts/cci/job.data.json \ No newline at end of file diff --git a/package.json b/package.json index 04939650..52c6e184 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,9 @@ "view-test-artifact": "./scripts/view-test-artifacts.sh", "cleanup-stale-resources": "source ./scripts/cloud-utils.sh && cleanupStaleResources", "cloud-e2e-cb": "source scripts/cloud-utils.sh && cloudE2E", - "cloud-e2e-cb-beta": "source scripts/cloud-utils.sh && cloudE2EBeta" + "cloud-e2e-cb-beta": "source scripts/cloud-utils.sh && cloudE2EBeta", + "split-codebuild-e2e-tests": "yarn ts-node ./scripts/split-e2e-tests-codebuild.ts && git add .codebuild/e2e_workflow.yml", + "update-test-timing-data": "ts-node ./scripts/cci/get-test-timing-from-cci-job-metrics.ts" }, "bugs": { "url": "https://github.com/aws-amplify/amplify-codegen/issues" @@ -55,7 +57,7 @@ "hooks": { "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", "pre-push": "npm run lint && npm run test-changed", - "pre-commit": "yarn split-e2e-tests && pretty-quick --staged" + "pre-commit": "yarn split-codebuild-e2e-tests" } }, "author": "Amazon Web Services", diff --git a/scripts/cci/api.ts b/scripts/cci/api.ts new file mode 100644 index 00000000..68c6ec56 --- /dev/null +++ b/scripts/cci/api.ts @@ -0,0 +1,117 @@ +import axios from 'axios'; + +type ReportingWindow = 'last-7-days' | 'last-90-days' | 'last-24-hours' | 'last-30-days' | 'last-60-days'; +export type CircleCIClientDefaults = { + defaultBranch: string; + defaultWorkflow: string; + vcs: string; + projectSlug: string; + projectName: string; +}; +export class CircleCIAPIClient { + private headers; + private options: CircleCIClientDefaults; + private slug: string; + constructor(token: string, options: CircleCIClientDefaults) { + this.headers = { + 'Circle-Token': token, + }; + this.options = options; + this.slug = `${options.vcs}/${options.projectSlug}/${options.projectName}`; + } + + /** + * Returns a sequence of jobs for a workflow. + * + * https://circleci.com/docs/api/v2/index.html#operation/listWorkflowJobs + * @returns + */ + getWorkflowJobs = async (workflowId: string = this.options.defaultWorkflow) => { + const result = await axios.get(`https://circleci.com/api/v2/workflow/${workflowId}/job`, { + headers: this.headers, + }); + return result.data; + }; + /** + * Returns a job's details. + * + * https://circleci.com/docs/api/v2/index.html#operation/getJobDetails + * @param jobId + * @returns + */ + getJobDetails = async (jobId: string) => { + const result = await axios.get(`https://circleci.com/api/v2/project/${this.slug}/job/${jobId}`, { + headers: this.headers, + }); + return result.data; + }; + /** + * Returns a single job's artifacts. + * + * https://circleci.com/docs/api/v2/index.html#operation/getJobArtifacts + * @param jobId + * @returns + */ + getJobArtifacts = async (jobId: string) => { + const result = await axios.get(`https://circleci.com/api/v2/project/${this.slug}/${jobId}/artifacts`, { + headers: this.headers, + }); + return result.data; + }; + /** + * Get test metadata for a single job + * + * https://circleci.com/docs/api/v2/index.html#operation/getTests + * @param jobId + * @returns + */ + getJobTests = async (jobId: string) => { + const result = await axios.get(`https://circleci.com/api/v2/project/${this.slug}/${jobId}/tests`, { + headers: this.headers, + }); + return result.data; + }; + /** + * Get summary metrics for a project workflow's jobs. + * + * https://circleci.com/docs/api/v2/index.html#operation/getProjectWorkflowJobMetrics + * + * @param workflowName + * @param branch + * @param reportingWindow + * @returns + */ + getAllJobMetrics = async ( + workflowName: string = this.options.defaultWorkflow, + branch: string = this.options.defaultBranch, + reportingWindow: ReportingWindow = 'last-30-days', + ) => { + const result = await axios.get(`https://circleci.com/api/v2/insights/${this.slug}/workflows/${workflowName}/jobs`, { + headers: this.headers, + params: { + branch: branch, + 'reporting-window': reportingWindow, + }, + }); + return result.data; + }; + + /** + * Get test metrics for a project's workflows. + * + * https://circleci.com/docs/api/v2/index.html#operation/getProjectWorkflowTestMetrics + * @param workflowName + * @param branch + * @param reportingWindow + * @returns + */ + getAllTestMetrics = async (workflowName: string = this.options.defaultWorkflow, branch: string = this.options.defaultBranch) => { + const result = await axios.get(`https://circleci.com/api/v2/insights/${this.slug}/workflows/${workflowName}/test-metrics`, { + headers: this.headers, + params: { + branch: branch, + }, + }); + return result.data; + }; +} \ No newline at end of file diff --git a/scripts/cci/get-test-timing-from-cci-job-metrics.ts b/scripts/cci/get-test-timing-from-cci-job-metrics.ts new file mode 100644 index 00000000..5ee83941 --- /dev/null +++ b/scripts/cci/get-test-timing-from-cci-job-metrics.ts @@ -0,0 +1,37 @@ +import { join } from 'path'; +import { REPO_ROOT, getCCIClient, getTestFiles, getTestNameFromPath, getTimingsFromJobsData, saveJobMetrics, saveTestTimings } from './utils'; + +async function main(): Promise { + const client = getCCIClient(); + console.log('Fetching job metrics...'); + const data = await client.getAllJobMetrics(); + saveJobMetrics(data); + + const testSuites = getTestFiles(join(REPO_ROOT, 'packages', 'amplify-codegen-e2e-tests')); + + const jobTimings = getTimingsFromJobsData(); + const testRuntimes = testSuites.map(t => { + const oldName = getTestNameFromPath(t); + if (jobTimings.has(oldName)) { + return { + test: t, + medianRuntime: jobTimings.get(oldName) as number, + }; + } else { + console.log('Could not find timing for:', t); + return { + test: t, + medianRuntime: 10, // default for unknown + }; + } + }); + testRuntimes.sort((a, b) => { + return a.medianRuntime - b.medianRuntime; + }); + saveTestTimings({ + lastUpdated: new Date().toISOString(), + totalTestFiles: testRuntimes.length, + timingData: testRuntimes, + }); +} +main(); \ No newline at end of file diff --git a/scripts/cci/test-timings.data.json b/scripts/cci/test-timings.data.json new file mode 100644 index 00000000..327302e7 --- /dev/null +++ b/scripts/cci/test-timings.data.json @@ -0,0 +1,114 @@ +{ + "lastUpdated": "2023-07-11T05:07:36.310Z", + "totalTestFiles": 27, + "timingData": [ + { + "test": "src/__tests__/add-codegen-ios.test.ts", + "medianRuntime": 6 + }, + { + "test": "src/__tests__/configure-codegen-android.test.ts", + "medianRuntime": 6 + }, + { + "test": "src/__tests__/configure-codegen-js.test.ts", + "medianRuntime": 6 + }, + { + "test": "src/__tests__/graphql-codegen-android.test.ts", + "medianRuntime": 6 + }, + { + "test": "src/__tests__/graphql-codegen-js.test.ts", + "medianRuntime": 6 + }, + { + "test": "src/__tests__/remove-codegen-android.test.ts", + "medianRuntime": 6 + }, + { + "test": "src/__tests__/remove-codegen-ios.test.ts", + "medianRuntime": 6 + }, + { + "test": "src/__tests__/add-codegen-android.test.ts", + "medianRuntime": 7 + }, + { + "test": "src/__tests__/configure-codegen-ios.test.ts", + "medianRuntime": 7 + }, + { + "test": "src/__tests__/datastore-modelgen-android.test.ts", + "medianRuntime": 7 + }, + { + "test": "src/__tests__/datastore-modelgen-js.test.ts", + "medianRuntime": 7 + }, + { + "test": "src/__tests__/feature-flags.test.ts", + "medianRuntime": 7 + }, + { + "test": "src/__tests__/graphql-codegen-ios.test.ts", + "medianRuntime": 7 + }, + { + "test": "src/__tests__/add-codegen-js.test.ts", + "medianRuntime": 8 + }, + { + "test": "src/__tests__/datastore-modelgen-ios.test.ts", + "medianRuntime": 8 + }, + { + "test": "src/__tests__/remove-codegen-js.test.ts", + "medianRuntime": 8 + }, + { + "test": "src/__tests__/datastore-modelgen-flutter.test.ts", + "medianRuntime": 9 + }, + { + "test": "src/__tests__/env-codegen.test.ts", + "medianRuntime": 9 + }, + { + "test": "src/__tests__/model-introspection-codegen.test.ts", + "medianRuntime": 9 + }, + { + "test": "src/__tests__/build-app-android.test.ts", + "medianRuntime": 11 + }, + { + "test": "src/__tests__/pull-codegen.test.ts", + "medianRuntime": 11 + }, + { + "test": "src/__tests__/push-codegen-ios.test.ts", + "medianRuntime": 11 + }, + { + "test": "src/__tests__/push-codegen-android.test.ts", + "medianRuntime": 12 + }, + { + "test": "src/__tests__/graphql-documents-generator.test.ts", + "medianRuntime": 13 + }, + { + "test": "src/__tests__/push-codegen-js.test.ts", + "medianRuntime": 13 + }, + { + "test": "src/__tests__/build-app-ts.test.ts", + "medianRuntime": 16 + }, + { + "test": "src/__tests__/build-app-swift.test.ts", + "medianRuntime": 20 + } + ] +} \ No newline at end of file diff --git a/scripts/cci/utils.ts b/scripts/cci/utils.ts new file mode 100644 index 00000000..469f1a6b --- /dev/null +++ b/scripts/cci/utils.ts @@ -0,0 +1,62 @@ +import { CircleCIAPIClient, CircleCIClientDefaults } from './api'; +import * as fs from 'fs-extra'; +import * as glob from 'glob'; +import { join } from 'path'; + +export const REPO_ROOT = join(__dirname, '..', '..'); +const JOB_METRICS_PATH = join(REPO_ROOT, 'scripts', 'cci', 'job.data.json'); +const TEST_TIMINGS_PATH = join(REPO_ROOT, 'scripts', 'cci', 'test-timings.data.json'); + +export const ClientDefaults: CircleCIClientDefaults = { + defaultBranch: 'main', + defaultWorkflow: 'build_test_deploy', + vcs: 'github', + projectSlug: 'aws-amplify', + projectName: 'amplify-codegen', +}; + +export const getCCIClient = () => { + if (!process.env.CIRCLECI_TOKEN) { + throw new Error('CIRCLECI_TOKEN is not set. Export it to your terminal, then try again.'); + } + return new CircleCIAPIClient(process.env.CIRCLECI_TOKEN, ClientDefaults); +} + +export function saveJobMetrics(data: any): any { + console.log(`saving job metrics to ${JOB_METRICS_PATH}`); + fs.writeFileSync(JOB_METRICS_PATH, JSON.stringify(data, null, 2)); +} + +export function getTestFiles(dir: string, pattern = 'src/**/*.test.ts'): string[] { + return glob.sync(pattern, { cwd: dir }); +} + +export function getTimingsFromJobsData() { + const jobData = JSON.parse(fs.readFileSync(JOB_METRICS_PATH, 'utf-8')); + const jobTimings: Map = new Map(); + for (let job of jobData.items) { + const testName = job.name; + const duration = Math.floor(job.metrics.duration_metrics.median / 60); + if (jobTimings.has(testName)) { + jobTimings.set(testName, Math.max(jobTimings.get(testName)!, duration)); + } else { + jobTimings.set(testName, duration); + } + } + return jobTimings; +} + +export const getTestNameFromPath = (testSuitePath: string): string => { + const startIndex = testSuitePath.lastIndexOf('/') + 1; + const endIndex = testSuitePath.lastIndexOf('.test'); + return testSuitePath + .substring(startIndex, endIndex) + .split('.') + .join('-') + + '-e2e-test'; +}; + +export function saveTestTimings(data: any): any { + console.log(`saving timing data to ${TEST_TIMINGS_PATH}`); + fs.writeFileSync(TEST_TIMINGS_PATH, JSON.stringify(data, null, 2)); +} \ No newline at end of file diff --git a/scripts/split-e2e-tests-codebuild.ts b/scripts/split-e2e-tests-codebuild.ts new file mode 100644 index 00000000..624df27f --- /dev/null +++ b/scripts/split-e2e-tests-codebuild.ts @@ -0,0 +1,206 @@ +import * as glob from 'glob'; +import * as fs from 'fs-extra'; +import { join } from 'path'; +import * as yaml from 'js-yaml'; + +// Ensure to update packages/amplify-codegen-e2e-tests/src/cleanup-e2e-resources.ts is also updated this gets updated +const AWS_REGIONS_TO_RUN_TESTS = [ + 'us-east-1', + 'us-east-2', + 'us-west-2', + 'eu-west-2', + 'eu-central-1', + 'ap-northeast-1', + 'ap-southeast-1', + 'ap-southeast-2', +]; + +// some tests require additional time, the parent account can handle longer tests (up to 90 minutes) +const USE_PARENT_ACCOUNT = []; +const REPO_ROOT = join(__dirname, '..'); +const TEST_TIMINGS_PATH = join(REPO_ROOT, 'scripts', 'cci', 'test-timings.data.json'); +const CODEBUILD_CONFIG_BASE_PATH = join(REPO_ROOT, '.codebuild', 'e2e_workflow_base.yml'); +const CODEBUILD_GENERATE_CONFIG_PATH = join(REPO_ROOT, '.codebuild', 'e2e_workflow.yml'); +const RUN_SOLO = []; +const EXCLUDE_TESTS = [ + 'src/__tests__/build-app-swift.test.ts', + 'src/__tests__/build-app-android.test.ts', +]; + +export function loadConfigBase() { + return yaml.load(fs.readFileSync(CODEBUILD_CONFIG_BASE_PATH, 'utf8')); +} +export function saveConfig(config: any): void { + const output = ['# auto generated file. DO NOT EDIT manually', yaml.dump(config, { noRefs: true })]; + fs.writeFileSync(CODEBUILD_GENERATE_CONFIG_PATH, output.join('\n')); +} +export function loadTestTimings(): { timingData: { test: string; medianRuntime: number }[] } { + return JSON.parse(fs.readFileSync(TEST_TIMINGS_PATH, 'utf-8')); +} +function getTestFiles(dir: string, pattern = 'src/**/*.test.ts'): string[] { + return glob.sync(pattern, { cwd: dir }); +} +type COMPUTE_TYPE = 'BUILD_GENERAL1_MEDIUM' | 'BUILD_GENERAL1_LARGE'; +type BatchBuildJob = { + identifier: string; + env: { + 'compute-type': COMPUTE_TYPE; + variables: [string: string]; + }; +}; +type ConfigBase = { + batch: { + 'build-graph': BatchBuildJob[]; + 'fast-fail': boolean; + }; + env: { + 'compute-type': COMPUTE_TYPE; + shell: 'bash'; + variables: [string: string]; + }; +}; +const MAX_WORKERS = 4; +type OS_TYPE = 'w' | 'l'; +type CandidateJob = { + region: string; + os: OS_TYPE; + tests: string[]; + useParentAccount: boolean; + runSolo: boolean; +}; +const createJob = (os: OS_TYPE, jobIdx: number, runSolo: boolean = false): CandidateJob => { + const region = AWS_REGIONS_TO_RUN_TESTS[jobIdx % AWS_REGIONS_TO_RUN_TESTS.length]; + return { + region, + os, + tests: [], + useParentAccount: false, + runSolo, + }; +}; +const getTestNameFromPath = (testSuitePath: string): string => { + const startIndex = testSuitePath.lastIndexOf('/') + 1; + const endIndex = testSuitePath.lastIndexOf('.test'); + return testSuitePath + .substring(startIndex, endIndex) + .split('.e2e') + .join('') + .split('.') + .join('-'); +}; +const splitTests = ( + baseJobLinux: any, + testDirectory: string, + pickTests?: ((testSuites: string[]) => string[]), +) => { + const output: any[] = []; + let testSuites = getTestFiles(testDirectory); + if (pickTests && typeof pickTests === 'function') { + testSuites = pickTests(testSuites); + } + if (testSuites.length === 0) { + return output; + } + const testFileRunTimes = loadTestTimings().timingData; + + testSuites.sort((a, b) => { + const runtimeA = testFileRunTimes.find((t:any) => t.test === a)?.medianRuntime ?? 30; + const runtimeB = testFileRunTimes.find((t:any) => t.test === b)?.medianRuntime ?? 30; + return runtimeA - runtimeB; + }); + const generateJobsForOS = (os: OS_TYPE) => { + const soloJobs: CandidateJob[] = []; + let jobIdx = 0; + const osJobs = [createJob(os, jobIdx)]; + jobIdx++; + for (let test of testSuites) { + const currentJob = osJobs[osJobs.length - 1]; + + const USE_PARENT = USE_PARENT_ACCOUNT.some((usesParent) => test.startsWith(usesParent)); + + if (RUN_SOLO.find((solo) => test === solo)) { + const newSoloJob = createJob(os, jobIdx, true); + jobIdx++; + newSoloJob.tests.push(test); + + if (USE_PARENT) { + newSoloJob.useParentAccount = true; + } + soloJobs.push(newSoloJob); + continue; + } + + // add the test + currentJob.tests.push(test); + + if (USE_PARENT) { + currentJob.useParentAccount = true; + } + + // create a new job once the current job is full; + if (currentJob.tests.length >= MAX_WORKERS) { + osJobs.push(createJob(os, jobIdx)); + jobIdx++; + } + } + return [...osJobs, ...soloJobs]; + }; + const linuxJobs = generateJobsForOS('l'); + const getIdentifier = (os: string, names: string) => { + const jobName = `${names.replace(/-/g, '_')}`.substring(0, 127); + return jobName; + }; + const result: any[] = []; + linuxJobs.forEach((j) => { + if (j.tests.length !== 0) { + const names = j.tests.map((tn) => getTestNameFromPath(tn)).join('_'); + const tmp = { + ...JSON.parse(JSON.stringify(baseJobLinux)), // deep clone base job + identifier: getIdentifier(j.os, names), + }; + tmp.env.variables = {}; + tmp.env.variables.TEST_SUITE = j.tests.join('|'); + tmp.env.variables.CLI_REGION = j.region; + if (j.useParentAccount) { + tmp.env.variables.USE_PARENT_ACCOUNT = 1; + } + if (j.runSolo) { + tmp.env['compute-type'] = 'BUILD_GENERAL1_SMALL'; + } + result.push(tmp); + } + }); + return result; +}; +function main(): void { + const configBase: any = loadConfigBase(); + const baseBuildGraph = configBase.batch['build-graph']; + const splitE2ETests = splitTests( + { + identifier: 'run_e2e_tests', + buildspec: '.codebuild/run_e2e_tests.yml', + env: { + 'compute-type': 'BUILD_GENERAL1_MEDIUM', + }, + 'depend-on': ['publish_to_local_registry'], + }, + join(REPO_ROOT, 'packages', 'amplify-codegen-e2e-tests'), + (testSuites) => testSuites.filter((ts) => !EXCLUDE_TESTS.includes(ts)), + ); + + let allBuilds = [...splitE2ETests]; + const cleanupResources = { + identifier: 'cleanup_e2e_resources', + buildspec: '.codebuild/cleanup_e2e_resources.yml', + env: { + 'compute-type': 'BUILD_GENERAL1_SMALL' + }, + 'depend-on': [allBuilds[0].identifier] + } + console.log(`Total number of splitted jobs: ${allBuilds.length}`) + let currentBatch = [...baseBuildGraph, ...allBuilds, cleanupResources]; + configBase.batch['build-graph'] = currentBatch; + saveConfig(configBase); +} + +main(); diff --git a/shared-scripts.sh b/shared-scripts.sh index cff95f36..c253edd5 100644 --- a/shared-scripts.sh +++ b/shared-scripts.sh @@ -142,17 +142,6 @@ function _publishToLocalRegistry { unsetNpmRegistryUrl # copy [verdaccio-cache] to s3 storeCache $CODEBUILD_SRC_DIR/../verdaccio-cache verdaccio-cache - - _generateChangeLog -} - -function _generateChangeLog { - echo "Generate Change Log" - git reset --hard HEAD - yarn update-versions - yarn ts-node scripts/unified-changelog.ts - # copy [changelog] to s3 - storeCacheFile $CODEBUILD_SRC_DIR/UNIFIED_CHANGELOG.md UNIFIED_CHANGELOG.md } function _installCLIFromLocalRegistry { @@ -311,7 +300,7 @@ function runE2eTest { if [ -z "$FIRST_RUN" ] || [ "$FIRST_RUN" == "true" ]; then echo "using Amplify CLI version: "$(amplify --version) - cd $(pwd)/packages/amplify-e2e-tests + cd $(pwd)/packages/amplify-codegen-e2e-tests fi if [ -f $FAILED_TEST_REGEX_FILE ]; then