diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa58ebf3..6bedc618 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,11 @@ jobs: run: yarn test:coverage - name: submit coverage - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v4 + with: + files: ./coverage/lcov.info + fail_ci_if_error: false + # token: ${{ secrets.CODECOV_TOKEN }} # Optional: add for more reliable uploads nodeX: name: Node ${{ matrix.node-version }} - ${{ matrix.os }} diff --git a/package.json b/package.json index 9e503edb..8e07d7a7 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "@typescript-eslint/eslint-plugin": "5.17.0", "@typescript-eslint/parser": "5.17.0", "broccoli-test-helper": "^2.0.0", + "cross-env": "^7.0.3", "cross-spawn": "7.0.3", "eslint": "8.12.0", "eslint-config-prettier": "8.5.0", @@ -118,7 +119,7 @@ "lint": "eslint \"./{src,test}/**/*.ts\"", "prepublish": "yarn clean && yarn compile", "test": "jest", - "test:coverage": "rimraf inst/ && nyc instrument lib inst && nyc --no-clean --reporter=clover --reporter=json --reporter=lcov yarn run jest --no-coverage", + "test:coverage": "rimraf inst/ && nyc instrument lib inst && cross-env ELS_COVERAGE=true nyc --no-clean --reporter=clover --reporter=json --reporter=lcov yarn run jest --no-coverage", "watch": "tsc --watch -p .", "prepare": "husky install", "test:worker-bundle": "npx http-server ." diff --git a/test/batman-fixture-based-integration-test.ts b/test/batman-fixture-based-integration-test.ts index 2f143b3d..f7fd0365 100644 --- a/test/batman-fixture-based-integration-test.ts +++ b/test/batman-fixture-based-integration-test.ts @@ -10,6 +10,7 @@ import { fsProjectToJSON, normalizeCompletionRequest, createConnection, + killServerProcess, } from './test_helpers/integration-helpers'; import { MessageConnection } from 'vscode-jsonrpc/node'; @@ -32,8 +33,8 @@ describe('With `batman project` initialized on server', () => { }); afterAll(async () => { - await connection.dispose(); - await serverProcess.kill(); + connection.dispose(); + await killServerProcess(serverProcess); }); beforeEach(async () => { diff --git a/test/fixture-based-integration-test.ts b/test/fixture-based-integration-test.ts index 47bee0a9..4eb5c3ad 100644 --- a/test/fixture-based-integration-test.ts +++ b/test/fixture-based-integration-test.ts @@ -9,6 +9,7 @@ import { normalizeUri, normalizeCompletionRequest, createConnection, + killServerProcess, } from './test_helpers/integration-helpers'; import { MessageConnection } from 'vscode-jsonrpc/node'; @@ -26,9 +27,9 @@ describe('With `full-project` initialized on server', () => { await new Promise((resolve) => setTimeout(resolve, 1000)); }); - afterAll(() => { + afterAll(async () => { connection.dispose(); - serverProcess.kill(); + await killServerProcess(serverProcess); }); beforeEach(async () => { diff --git a/test/integration-test.ts b/test/integration-test.ts index 1bb7f76f..2cfe5549 100644 --- a/test/integration-test.ts +++ b/test/integration-test.ts @@ -18,6 +18,7 @@ import { asyncFSProvider, registerCommandExecutor, createConnection, + killServerProcess, } from './test_helpers/integration-helpers'; import { MessageConnection } from 'vscode-jsonrpc/node'; @@ -67,8 +68,8 @@ describe('integration', function () { asyncFSProviderInstance = null; } - await connection.dispose(); - await serverProcess.kill(); + connection.dispose(); + await killServerProcess(serverProcess); }); describe('Initialize request', () => { diff --git a/test/test_helpers/integration-helpers.ts b/test/test_helpers/integration-helpers.ts index c221c5ad..bd5561b2 100644 --- a/test/test_helpers/integration-helpers.ts +++ b/test/test_helpers/integration-helpers.ts @@ -32,6 +32,20 @@ import { FileStat, FileType, fileTypeFromFsStat } from '../../src/utils/fs-utils import { IRegistry } from '../../src/utils/registry-api'; import { Readable, Writable } from 'stream'; +export async function killServerProcess(serverProcess: cp.ChildProcess): Promise { + if (!serverProcess.killed) { + await new Promise((resolve) => { + const timeout = setTimeout(() => resolve(), 2000); + + serverProcess.once('exit', () => { + clearTimeout(timeout); + resolve(); + }); + serverProcess.kill(); + }); + } +} + export function createConnection(serverProcess: cp.ChildProcess) { serverProcess.stderr.on('data', (data) => { fs.appendFileSync(`./error.${serverProcess.pid || 0}.log`, data); @@ -59,23 +73,22 @@ export function createConnection(serverProcess: cp.ChildProcess) { } export function startServer(asyncFs = false) { - const params = JSON.parse(process.env.npm_config_argv); - - // { - // remain: [], - // cooked: [ 'run', 'test' ], - // original: [ 'test', 'template-imports-integration' ] - // } - const command = params.original[0]; + // Use ELS_COVERAGE environment variable to determine if we're running in coverage mode + // This is more reliable than parsing npm_config_argv which is deprecated in newer yarn versions + const isCoverageMode = process.env.ELS_COVERAGE === 'true'; let serverPath = ''; - if (command === 'test') { - serverPath = './lib/start-server.js'; - } else { + if (isCoverageMode) { + // Use instrumented code for coverage collection serverPath = './inst/start-server.js'; + } else { + // Use regular compiled code for normal test runs + serverPath = './lib/start-server.js'; } + // Always use nyc to spawn the server for consistent stdio handling + // The --reporter none option prevents nyc from outputting reports inline const options: Array = ['--reporter', 'none', 'node', serverPath, '--stdio', asyncFs ? '--async-fs' : undefined, '--no-clean']; return spawn( diff --git a/test/test_helpers/public-integration-helpers.ts b/test/test_helpers/public-integration-helpers.ts index ce64dede..e69cea73 100644 --- a/test/test_helpers/public-integration-helpers.ts +++ b/test/test_helpers/public-integration-helpers.ts @@ -1,5 +1,5 @@ import { Disposable, MessageConnection } from 'vscode-jsonrpc'; -import { asyncFSProvider, initServer, registerCommandExecutor, startServer, createConnection } from './integration-helpers'; +import { asyncFSProvider, initServer, registerCommandExecutor, startServer, createConnection, killServerProcess } from './integration-helpers'; export { getResult, makeProject, createProject } from './integration-helpers'; @@ -53,8 +53,8 @@ export async function createServer({ asyncFsEnabled } = { asyncFsEnabled: false asyncFSProviderInstance = null; } - await connection.dispose(); - await serverProcess.kill(); + connection.dispose(); + await killServerProcess(serverProcess); }, }; } diff --git a/yarn.lock b/yarn.lock index 366ef6db..3cac21d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2203,6 +2203,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" +cross-env@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -2223,6 +2230,15 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^7.0.1: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + crypto-browserify@^3.12.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"