Github 操作上的 Mocha 并行作业

Mocha Parallel Jobs on Github Actions

我刚切换到 Mocha 中的并行测试,效果很好,但是,当我 运行 在 github 上使用它时,它似乎没有 运行ning在平行下。我需要配置什么才能让 Mocha 在 Github 操作上并行 运行?

.mocharc.json // 摩卡 9.x

{
  "timeout": 5000,
  "recursive": true,
  "ui": "mocha-cakes-2",
  "parallel": true,
  "jobs": 4,
  "checkLeaks": true,
  "globals": [
    "browser",
    "mocha-cakes-2",
    "regeneratorRuntime"
  ],
  "exit": true,
  "require": ["./test/helpers/setup.js"]
}

node.js.yml

name: Node.js CI

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [14.x]

    steps:
      - uses: actions/checkout@v2

      - name: Cache node modules
        uses: actions/cache@v2
        env:
          cache-name: cache-node-modules
        with:
          # npm cache files are stored in `~/.npm` on Linux/macOS
          path: ~/.npm
          key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-build-${{ env.cache-name }}-
            ${{ runner.os }}-build-
            ${{ runner.os }}-
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}

      - run: npm ci
      - run: npm run build --if-present
      - run: npm test

我通过以下方式让它工作:

  • 通过findAllTests.js
  • 抓取所有测试文件
  • 然后我用 jq 将这个文件的输出分成 3 个相等的块。
  • 然后我把npm run build的神器存起来,准备在下一阶段使用。
  • 然后我连续启动 3 个测试运行器 (随着项目的增长很容易配置)
  • 最后,无论前面的任何步骤是否失败,我都会删除工件(我们不需要它们)

findAllTests.js

"use strict";

const fs = require("fs");
const path = require("path");

const files = [];

function shuffleArray(unshuffledArray) {
  // Schwartzian transform. Will eventually help in evening out the test-time for each chunk.
  return unshuffledArray
    .map((a) => ({ sort: Math.random(), value: a }))
    .sort((a, b) => a.sort - b.sort)
    .map((a) => a.value);
}

/**
 * Traverses through all subdirectories of a given folder.
 * Used for Github Actions in order to aggregate and chunk all tests into even sizes.
 */

function findAllTests(directory) {
  fs.readdirSync(directory).forEach((file) => {
    const absolute = path.join(directory, file);
    if (fs.statSync(absolute).isDirectory()) {
      return findAllTests(absolute);
    }
    if (absolute.endsWith(".js") && !/(findAllTests|\/data\/|\/helpers\/)/.test(absolute)) {
      return files.push(absolute);
    }
  });
}

function isMocha(context) {
  return ["afterEach", "after", "beforeEach", "before", "describe", "it"].every((functionName) => {
    return context[functionName] instanceof Function;
  });
}

if (!isMocha(global)) {
  findAllTests("./test/");
  console.log(JSON.stringify(shuffleArray(files), null, 2)); // eslint-disable-line
}

node.yml.js

name: Node.js CI

on: [push]

jobs:
  setup:
    runs-on: ubuntu-latest
    name: Setup
    strategy:
      matrix:
        node-version: [14.x]
    outputs:
      test-chunks: ${{ steps['set-test-chunks'].outputs['test-chunks'] }}
      test-chunk-ids: ${{ steps['set-test-chunk-ids'].outputs['test-chunk-ids'] }}
    steps:
      - uses: actions/checkout@v2
      - name: Cache node modules
        uses: actions/cache@v2
        env:
          cache-name: cache-node-modules
        with:
          path: ~/.npm
          key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-build-${{ env.cache-name }}-
            ${{ runner.os }}-build-
      - name: Install & Build
        run: |
          npm ci
          npm run build --if-present
      - name: ESLint
        run: npm run lint:js
      - name: Save build artifacts
        uses: actions/upload-artifact@v2
        with:
          name: public
          path: public
      - id: set-test-chunks
        name: Set Chunks
        run: echo "::set-output name=test-chunks::$(node ./test/findAllTests.js | jq -cM '[_nwise(length / 3 | ceil)]')"
      - id: set-test-chunk-ids
        name: Set Chunk IDs
        run: echo "::set-output name=test-chunk-ids::$(echo $CHUNKS | jq -cM 'to_entries | map(.key)')"
        env:
          CHUNKS: ${{ steps['set-test-chunks'].outputs['test-chunks'] }}
  mocha:
    runs-on: ubuntu-latest
    name: Test (chunk ${{ matrix.chunk }})
    needs: setup
    strategy:
      matrix:
        node-version: [14.x]
        chunk: ${{ fromJson(needs.setup.outputs['test-chunk-ids']) }}
    steps:
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}
      - uses: actions/checkout@v2
      - run: npm ci
      - name: Download build artifacts
        uses: actions/download-artifact@v2
      - name: Mocha
        run: echo $CHUNKS | jq '.[${{ matrix.chunk }}] | .[] | @text' | xargs npm run mocha:ga
        env:
          CHUNKS: ${{ needs.setup.outputs['test-chunks'] }}
  cleanup:
    runs-on: ubuntu-latest
    name: Cleanup
    needs: [setup, mocha]
    if: always()
    steps:
      - uses: geekyeggo/delete-artifact@v1
        with:
          name: public
          failOnError: false

mocharc.json

{
  "timeout": 5000,
  "recursive": true,
  "ui": "mocha-cakes-2",
  "checkLeaks": true,
  "globals": [
    "browser",
    "document",
    "mocha-cakes-2",
    "regeneratorRuntime",
    "WebSocket",
    "window"
  ],
  "exit": true,
  "no-warnings": true,
  "require": ["./test/helpers/setup.js"]
}