Code Coverage Support (#182)

* Added basic framework for enable code coverage

* Added basic coverage results building and combination

* fixed ENABLE_CODE_COVERAGE to be `true` or `false`

* Added code coverage project to manifest

* Updated to add more tests for code coverage build

* Updated coverage parameter documentation

* Fixed small syntax error

* Enabled code coverage flag for code coverage tests

* Fixed error in test file build

* Updated project run settings

* Fixed error when creating combined code coverage results

* Updated testing workflows

* updated test workflows

* Updated parameters and added tests

* Updated tests and bash script for running

* Updated run_tests.sh script to simplfy some parameters

* Updated run_tests to remove incorrect ';'

* Updated run_tests script

* Fixed small syntax error

* Fixed for loop in run_tests.sh

* Updated run_tests syntax error for '=' operator

* Fixed runTests variable assignment

* Fixed parameters for running tests via bash

* Corrected bash arguments

* Updated test cases in main.yml

* Updated parameter names and default values for code coverage

* Fixed broken paths for coverage results upload in main.yml

* Corrected names of coverage results artifacts
pull/183/head v2.0.1
Nick Maltbie 2022-04-21 04:50:37 -04:00 committed by GitHub
parent ec4f39253f
commit 96562463cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 117 additions and 110 deletions

View File

@ -105,6 +105,7 @@ jobs:
projectPath: ${{ matrix.projectPath }} projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }} unityVersion: ${{ matrix.unityVersion }}
testMode: all testMode: all
coverageOptions: 'enableCyclomaticComplexity;generateHtmlReport;generateBadgeReport;assemblyFilters:+MyScripts'
# Test implicit artifactsPath, by not setting it # Test implicit artifactsPath, by not setting it
# Upload artifacts # Upload artifacts
@ -115,6 +116,14 @@ jobs:
path: ${{ steps.allTests.outputs.artifactsPath }} path: ${{ steps.allTests.outputs.artifactsPath }}
retention-days: 14 retention-days: 14
# Upload coverage
- name: Upload coverage results
uses: actions/upload-artifact@v3
with:
name: Coverage results (all)
path: ${{ steps.allTests.outputs.coveragePath }}
retention-days: 14
testRunnerInEditMode: testRunnerInEditMode:
name: Test edit mode 📝 name: Test edit mode 📝
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -162,6 +171,14 @@ jobs:
path: ${{ steps.editMode.outputs.artifactsPath }} path: ${{ steps.editMode.outputs.artifactsPath }}
retention-days: 14 retention-days: 14
# Upload coverage
- name: Upload coverage results
uses: actions/upload-artifact@v3
with:
name: Coverage results (edit mode)
path: ${{ steps.editMode.outputs.coveragePath }}
retention-days: 14
testRunnerInPlayMode: testRunnerInPlayMode:
name: Test play mode 📺 name: Test play mode 📺
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -209,6 +226,14 @@ jobs:
path: ${{ steps.playMode.outputs.artifactsPath }} path: ${{ steps.playMode.outputs.artifactsPath }}
retention-days: 14 retention-days: 14
# Upload coverage
- name: Upload coverage results
uses: actions/upload-artifact@v3
with:
name: Coverage results (play mode)
path: ${{ steps.playMode.outputs.coveragePath }}
retention-days: 14
testEachModeSequentially: testEachModeSequentially:
name: Test each mode sequentially 👩‍👩‍👧‍👦 # don't try this at home (it's much slower) name: Test each mode sequentially 👩‍👩‍👧‍👦 # don't try this at home (it's much slower)
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -20,6 +20,10 @@ inputs:
required: false required: false
default: 'all' default: 'all'
description: 'The type of tests to be run by the test runner.' description: 'The type of tests to be run by the test runner.'
coverageOptions:
required: false
default: 'enableCyclomaticComplexity;generateHtmlReport;generateBadgeReport'
description: 'Optional coverage parameters for the -coverageOptions argument.'
artifactsPath: artifactsPath:
required: false required: false
default: 'artifacts' default: 'artifacts'
@ -46,7 +50,9 @@ inputs:
description: 'Name for the check run that is created when a github token is provided.' description: 'Name for the check run that is created when a github token is provided.'
outputs: outputs:
artifactsPath: artifactsPath:
description: 'Path where the artifacts are stored' description: 'Path where the artifacts are stored.'
coveragePath:
description: 'Path where the code coverage results are stored.'
branding: branding:
icon: 'box' icon: 'box'
color: 'gray-dark' color: 'gray-dark'

18
dist/index.js generated vendored
View File

@ -42,7 +42,7 @@ function run() {
try { try {
model_1.Action.checkCompatibility(); model_1.Action.checkCompatibility();
const { workspace, actionFolder } = model_1.Action; const { workspace, actionFolder } = model_1.Action;
const { editorVersion, customImage, projectPath, customParameters, testMode, artifactsPath, useHostNetwork, sshAgent, gitPrivateToken, githubToken, checkName, } = model_1.Input.getFromUser(); const { editorVersion, customImage, projectPath, customParameters, testMode, coverageOptions, artifactsPath, useHostNetwork, sshAgent, gitPrivateToken, githubToken, checkName, } = model_1.Input.getFromUser();
const baseImage = new model_1.ImageTag({ editorVersion, customImage }); const baseImage = new model_1.ImageTag({ editorVersion, customImage });
const runnerTemporaryPath = process.env.RUNNER_TEMP; const runnerTemporaryPath = process.env.RUNNER_TEMP;
try { try {
@ -53,6 +53,7 @@ function run() {
projectPath, projectPath,
customParameters, customParameters,
testMode, testMode,
coverageOptions,
artifactsPath, artifactsPath,
useHostNetwork, useHostNetwork,
sshAgent, sshAgent,
@ -63,6 +64,7 @@ function run() {
} }
finally { finally {
yield model_1.Output.setArtifactsPath(artifactsPath); yield model_1.Output.setArtifactsPath(artifactsPath);
yield model_1.Output.setCoveragePath('CodeCoverage');
} }
if (githubToken) { if (githubToken) {
const failedTestCount = yield model_1.ResultsCheck.createCheck(artifactsPath, githubToken, checkName); const failedTestCount = yield model_1.ResultsCheck.createCheck(artifactsPath, githubToken, checkName);
@ -152,13 +154,14 @@ const path_1 = __importDefault(__nccwpck_require__(1017));
const Docker = { const Docker = {
run(image, parameters, silent = false) { run(image, parameters, silent = false) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const { actionFolder, editorVersion, workspace, projectPath, customParameters, testMode, artifactsPath, useHostNetwork, sshAgent, gitPrivateToken, githubToken, runnerTemporaryPath, } = parameters; const { actionFolder, editorVersion, workspace, projectPath, customParameters, testMode, coverageOptions, artifactsPath, useHostNetwork, sshAgent, gitPrivateToken, githubToken, runnerTemporaryPath, } = parameters;
const githubHome = path_1.default.join(runnerTemporaryPath, '_github_home'); const githubHome = path_1.default.join(runnerTemporaryPath, '_github_home');
if (!(0, fs_1.existsSync)(githubHome)) if (!(0, fs_1.existsSync)(githubHome))
(0, fs_1.mkdirSync)(githubHome); (0, fs_1.mkdirSync)(githubHome);
const githubWorkflow = path_1.default.join(runnerTemporaryPath, '_github_workflow'); const githubWorkflow = path_1.default.join(runnerTemporaryPath, '_github_workflow');
if (!(0, fs_1.existsSync)(githubWorkflow)) if (!(0, fs_1.existsSync)(githubWorkflow))
(0, fs_1.mkdirSync)(githubWorkflow); (0, fs_1.mkdirSync)(githubWorkflow);
const testPlatforms = (testMode === 'all' ? ['playmode', 'editmode', 'COMBINE_RESULTS'] : [testMode]).join(';');
const command = `docker run \ const command = `docker run \
--workdir /github/workspace \ --workdir /github/workspace \
--rm \ --rm \
@ -170,7 +173,9 @@ const Docker = {
--env UNITY_VERSION="${editorVersion}" \ --env UNITY_VERSION="${editorVersion}" \
--env PROJECT_PATH="${projectPath}" \ --env PROJECT_PATH="${projectPath}" \
--env CUSTOM_PARAMETERS="${customParameters}" \ --env CUSTOM_PARAMETERS="${customParameters}" \
--env TEST_MODE="${testMode}" \ --env TEST_PLATFORMS="${testPlatforms}" \
--env COVERAGE_OPTIONS="${coverageOptions}" \
--env COVERAGE_RESULTS_PATH="CodeCoverage" \
--env ARTIFACTS_PATH="${artifactsPath}" \ --env ARTIFACTS_PATH="${artifactsPath}" \
--env GITHUB_REF \ --env GITHUB_REF \
--env GITHUB_SHA \ --env GITHUB_SHA \
@ -385,6 +390,7 @@ const Input = {
const rawProjectPath = (0, core_1.getInput)('projectPath') || '.'; const rawProjectPath = (0, core_1.getInput)('projectPath') || '.';
const customParameters = (0, core_1.getInput)('customParameters') || ''; const customParameters = (0, core_1.getInput)('customParameters') || '';
const testMode = ((0, core_1.getInput)('testMode') || 'all').toLowerCase(); const testMode = ((0, core_1.getInput)('testMode') || 'all').toLowerCase();
const coverageOptions = (0, core_1.getInput)('coverageOptions') || '';
const rawArtifactsPath = (0, core_1.getInput)('artifactsPath') || 'artifacts'; const rawArtifactsPath = (0, core_1.getInput)('artifactsPath') || 'artifacts';
const rawUseHostNetwork = (0, core_1.getInput)('useHostNetwork') || 'false'; const rawUseHostNetwork = (0, core_1.getInput)('useHostNetwork') || 'false';
const sshAgent = (0, core_1.getInput)('sshAgent') || ''; const sshAgent = (0, core_1.getInput)('sshAgent') || '';
@ -416,6 +422,7 @@ const Input = {
projectPath, projectPath,
customParameters, customParameters,
testMode, testMode,
coverageOptions,
artifactsPath, artifactsPath,
useHostNetwork, useHostNetwork,
sshAgent, sshAgent,
@ -471,6 +478,11 @@ const Output = {
yield core.setOutput('artifactsPath', artifactsPath); yield core.setOutput('artifactsPath', artifactsPath);
}); });
}, },
setCoveragePath(coveragePath) {
return __awaiter(this, void 0, void 0, function* () {
yield core.setOutput('coveragePath', coveragePath);
});
},
}; };
exports["default"] = Output; exports["default"] = Output;

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

View File

@ -14,27 +14,18 @@ echo "Using project path \"$UNITY_PROJECT_PATH\"."
echo "Using artifacts path \"$ARTIFACTS_PATH\" to save test results." echo "Using artifacts path \"$ARTIFACTS_PATH\" to save test results."
FULL_ARTIFACTS_PATH=$GITHUB_WORKSPACE/$ARTIFACTS_PATH FULL_ARTIFACTS_PATH=$GITHUB_WORKSPACE/$ARTIFACTS_PATH
#
# Set and display the coverage results path
#
echo "Using coverage results path \"$COVERAGE_RESULTS_PATH\" to save test coverage results."
FULL_COVERAGE_RESULTS_PATH=$GITHUB_WORKSPACE/$COVERAGE_RESULTS_PATH
# #
# Display custom parameters # Display custom parameters
# #
echo "Using custom parameters $CUSTOM_PARAMETERS."
# Set the modes for testing echo "Using custom parameters $CUSTOM_PARAMETERS."
case $TEST_MODE in
editmode)
echo "Edit mode selected for testing."
EDIT_MODE=true
;;
playmode)
echo "Play mode selected for testing."
PLAY_MODE=true
;;
*)
echo "All modes selected for testing."
EDIT_MODE=true
PLAY_MODE=true
;;
esac
# The following tests are 2019 mode (requires Unity 2019.2.11f1 or later) # The following tests are 2019 mode (requires Unity 2019.2.11f1 or later)
# Reference: https://docs.unity3d.com/2019.3/Documentation/Manual/CommandLineArguments.html # Reference: https://docs.unity3d.com/2019.3/Documentation/Manual/CommandLineArguments.html
@ -65,118 +56,61 @@ echo ""
ls -alh $UNITY_PROJECT_PATH ls -alh $UNITY_PROJECT_PATH
# #
# Testing in EditMode # Testing for each platform
# #
EDIT_MODE_EXIT_CODE=0 for platform in ${TEST_PLATFORMS//;/ }; do
if [ "$EDIT_MODE" = "true" ]; then
echo "" echo ""
echo "###########################" echo "###########################"
echo "# Testing in EditMode #" echo "# Testing in $platform #"
echo "###########################" echo "###########################"
echo "" echo ""
if [[ "$platform" != "COMBINE_RESULTS" ]]; then
runTests="-runTests -testPlatform $platform -testResults $FULL_ARTIFACTS_PATH/$platform-results.xml"
else
runTests="-quit"
fi
unity-editor \ unity-editor \
-batchmode \ -batchmode \
-logFile "$FULL_ARTIFACTS_PATH/editmode.log" \ -logFile "$FULL_ARTIFACTS_PATH/$platform.log" \
-projectPath "$UNITY_PROJECT_PATH" \ -projectPath "$UNITY_PROJECT_PATH" \
-runTests \ -coverageResultsPath "$FULL_COVERAGE_RESULTS_PATH" \
-testPlatform editmode \ $runTests \
-testResults "$FULL_ARTIFACTS_PATH/editmode-results.xml" \ -enableCodeCoverage \
-debugCodeOptimization \
-coverageOptions "$COVERAGE_OPTIONS" \
$CUSTOM_PARAMETERS $CUSTOM_PARAMETERS
# Catch exit code # Catch exit code
EDIT_MODE_EXIT_CODE=$? TEST_EXIT_CODE=$?
# Print unity log output # Print unity log output
cat "$FULL_ARTIFACTS_PATH/editmode.log" cat "$FULL_ARTIFACTS_PATH/$platform.log"
# Display results # Display results
if [ $EDIT_MODE_EXIT_CODE -eq 0 ]; then if [ $TEST_EXIT_CODE -eq 0 ]; then
echo "Run succeeded, no failures occurred"; echo "Run succeeded, no failures occurred";
elif [ $EDIT_MODE_EXIT_CODE -eq 2 ]; then elif [ $TEST_EXIT_CODE -eq 2 ]; then
echo "Run succeeded, some tests failed"; echo "Run succeeded, some tests failed";
elif [ $EDIT_MODE_EXIT_CODE -eq 3 ]; then elif [ $TEST_EXIT_CODE -eq 3 ]; then
echo "Run failure (other failure)"; echo "Run failure (other failure)";
else else
echo "Unexpected exit code $EDIT_MODE_EXIT_CODE"; echo "Unexpected exit code $TEST_EXIT_CODE";
fi fi
fi
# if [ $TEST_EXIT_CODE -ne 0 ]; then
# Testing in PlayMode TEST_RUNNER_EXIT_CODE=$TEST_EXIT_CODE
#
PLAY_MODE_EXIT_CODE=0
if [ "$PLAY_MODE" = "true" ]; then
echo ""
echo "###########################"
echo "# Testing in PlayMode #"
echo "###########################"
echo ""
unity-editor \
-batchmode \
-logFile "$FULL_ARTIFACTS_PATH/playmode.log" \
-projectPath "$UNITY_PROJECT_PATH" \
-runTests \
-testPlatform playmode \
-testResults "$FULL_ARTIFACTS_PATH/playmode-results.xml" \
$CUSTOM_PARAMETERS
# Catch exit code
PLAY_MODE_EXIT_CODE=$?
# Print unity log output
cat "$FULL_ARTIFACTS_PATH/playmode.log"
# Display results
if [ $PLAY_MODE_EXIT_CODE -eq 0 ]; then
echo "Run succeeded, no failures occurred";
elif [ $PLAY_MODE_EXIT_CODE -eq 2 ]; then
echo "Run succeeded, some tests failed";
elif [ $PLAY_MODE_EXIT_CODE -eq 3 ]; then
echo "Run failure (other failure)";
else
echo "Unexpected exit code $PLAY_MODE_EXIT_CODE";
fi fi
fi
#
# Results
#
echo ""
echo "###########################"
echo "# Project directory #"
echo "###########################"
echo ""
ls -alh $UNITY_PROJECT_PATH
if [ "$EDIT_MODE" = "true" ]; then
echo "" echo ""
echo "###########################" echo "###########################"
echo "# Edit Mode Results #" echo "# $platform Results #"
echo "###########################" echo "###########################"
echo "" echo ""
cat "$FULL_ARTIFACTS_PATH/editmode-results.xml"
cat "$FULL_ARTIFACTS_PATH/editmode-results.xml" | grep test-run | grep Passed
fi
if [ "$PLAY_MODE" = "true" ]; then if [[ "$platform" != "COMBINE_RESULTS" ]]; then
echo "" cat "$FULL_ARTIFACTS_PATH/$platform-results.xml"
echo "###########################" cat "$FULL_ARTIFACTS_PATH/$platform-results.xml" | grep test-run | grep Passed
echo "# Play Mode Results #" fi
echo "###########################" done
echo ""
cat "$FULL_ARTIFACTS_PATH/playmode-results.xml"
cat "$FULL_ARTIFACTS_PATH/playmode-results.xml" | grep test-run | grep Passed
fi
#
# Exit
#
if [ $EDIT_MODE_EXIT_CODE -gt 0 ]; then
TEST_RUNNER_EXIT_CODE=$EDIT_MODE_EXIT_CODE
fi
if [ $PLAY_MODE_EXIT_CODE -gt 0 ]; then
TEST_RUNNER_EXIT_CODE=$PLAY_MODE_EXIT_CODE
fi

View File

@ -12,6 +12,7 @@ async function run() {
projectPath, projectPath,
customParameters, customParameters,
testMode, testMode,
coverageOptions,
artifactsPath, artifactsPath,
useHostNetwork, useHostNetwork,
sshAgent, sshAgent,
@ -30,6 +31,7 @@ async function run() {
projectPath, projectPath,
customParameters, customParameters,
testMode, testMode,
coverageOptions,
artifactsPath, artifactsPath,
useHostNetwork, useHostNetwork,
sshAgent, sshAgent,
@ -39,6 +41,7 @@ async function run() {
}); });
} finally { } finally {
await Output.setArtifactsPath(artifactsPath); await Output.setArtifactsPath(artifactsPath);
await Output.setCoveragePath('CodeCoverage');
} }
if (githubToken) { if (githubToken) {

View File

@ -11,6 +11,7 @@ const Docker = {
projectPath, projectPath,
customParameters, customParameters,
testMode, testMode,
coverageOptions,
artifactsPath, artifactsPath,
useHostNetwork, useHostNetwork,
sshAgent, sshAgent,
@ -23,6 +24,9 @@ const Docker = {
if (!existsSync(githubHome)) mkdirSync(githubHome); if (!existsSync(githubHome)) mkdirSync(githubHome);
const githubWorkflow = path.join(runnerTemporaryPath, '_github_workflow'); const githubWorkflow = path.join(runnerTemporaryPath, '_github_workflow');
if (!existsSync(githubWorkflow)) mkdirSync(githubWorkflow); if (!existsSync(githubWorkflow)) mkdirSync(githubWorkflow);
const testPlatforms = (
testMode === 'all' ? ['playmode', 'editmode', 'COMBINE_RESULTS'] : [testMode]
).join(';');
const command = `docker run \ const command = `docker run \
--workdir /github/workspace \ --workdir /github/workspace \
@ -35,7 +39,9 @@ const Docker = {
--env UNITY_VERSION="${editorVersion}" \ --env UNITY_VERSION="${editorVersion}" \
--env PROJECT_PATH="${projectPath}" \ --env PROJECT_PATH="${projectPath}" \
--env CUSTOM_PARAMETERS="${customParameters}" \ --env CUSTOM_PARAMETERS="${customParameters}" \
--env TEST_MODE="${testMode}" \ --env TEST_PLATFORMS="${testPlatforms}" \
--env COVERAGE_OPTIONS="${coverageOptions}" \
--env COVERAGE_RESULTS_PATH="CodeCoverage" \
--env ARTIFACTS_PATH="${artifactsPath}" \ --env ARTIFACTS_PATH="${artifactsPath}" \
--env GITHUB_REF \ --env GITHUB_REF \
--env GITHUB_SHA \ --env GITHUB_SHA \

View File

@ -19,6 +19,7 @@ const Input = {
const rawProjectPath = getInput('projectPath') || '.'; const rawProjectPath = getInput('projectPath') || '.';
const customParameters = getInput('customParameters') || ''; const customParameters = getInput('customParameters') || '';
const testMode = (getInput('testMode') || 'all').toLowerCase(); const testMode = (getInput('testMode') || 'all').toLowerCase();
const coverageOptions = getInput('coverageOptions') || '';
const rawArtifactsPath = getInput('artifactsPath') || 'artifacts'; const rawArtifactsPath = getInput('artifactsPath') || 'artifacts';
const rawUseHostNetwork = getInput('useHostNetwork') || 'false'; const rawUseHostNetwork = getInput('useHostNetwork') || 'false';
const sshAgent = getInput('sshAgent') || ''; const sshAgent = getInput('sshAgent') || '';
@ -57,6 +58,7 @@ const Input = {
projectPath, projectPath,
customParameters, customParameters,
testMode, testMode,
coverageOptions,
artifactsPath, artifactsPath,
useHostNetwork, useHostNetwork,
sshAgent, sshAgent,

View File

@ -6,4 +6,19 @@ describe('Output', () => {
await expect(Output.setArtifactsPath('')).resolves.not.toThrow(); await expect(Output.setArtifactsPath('')).resolves.not.toThrow();
}); });
}); });
describe('setCoveragePath', () => {
it('does not throw', async () => {
await expect(Output.setCoveragePath('')).resolves.not.toThrow();
await expect(Output.setCoveragePath('artifacts')).resolves.not.toThrow();
await expect(Output.setCoveragePath('coverage')).resolves.not.toThrow();
await expect(Output.setCoveragePath('CodeCoverage')).resolves.not.toThrow();
await expect(Output.setCoveragePath('./artifacts')).resolves.not.toThrow();
await expect(Output.setCoveragePath('./coverage')).resolves.not.toThrow();
await expect(Output.setCoveragePath('./CodeCoverage')).resolves.not.toThrow();
await expect(Output.setCoveragePath('./artifacts/coverage')).resolves.not.toThrow();
await expect(Output.setCoveragePath('./coverage/')).resolves.not.toThrow();
await expect(Output.setCoveragePath('./CodeCoverage/')).resolves.not.toThrow();
await expect(Output.setCoveragePath('./artifacts/coverage/')).resolves.not.toThrow();
});
});
}); });

View File

@ -4,6 +4,9 @@ const Output = {
async setArtifactsPath(artifactsPath) { async setArtifactsPath(artifactsPath) {
await core.setOutput('artifactsPath', artifactsPath); await core.setOutput('artifactsPath', artifactsPath);
}, },
async setCoveragePath(coveragePath) {
await core.setOutput('coveragePath', coveragePath);
},
}; };
export default Output; export default Output;

View File

@ -6,6 +6,7 @@
"com.unity.ide.vscode": "1.1.2", "com.unity.ide.vscode": "1.1.2",
"com.unity.package-manager-ui": "2.2.0", "com.unity.package-manager-ui": "2.2.0",
"com.unity.test-framework": "1.0.13", "com.unity.test-framework": "1.0.13",
"com.unity.testtools.codecoverage": "1.0.1",
"com.unity.textmeshpro": "2.0.1", "com.unity.textmeshpro": "2.0.1",
"com.unity.timeline": "1.1.0", "com.unity.timeline": "1.1.0",
"com.unity.ugui": "1.0.0", "com.unity.ugui": "1.0.0",