mirror of
https://github.com/game-ci/unity-test-runner.git
synced 2026-01-29 22:49:12 +08:00
Compare commits
111 Commits
v1
...
v2.0-alpha
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
694c315276 | ||
|
|
fffbf11b2a | ||
|
|
a195213baa | ||
|
|
8033066291 | ||
|
|
215f660c06 | ||
|
|
43d90c252f | ||
|
|
345f4c64bd | ||
|
|
d45ca4403f | ||
|
|
7dd059111b | ||
|
|
5edc17bb8a | ||
|
|
23b6b8f5f3 | ||
|
|
6e30d4827d | ||
|
|
50e6471ee4 | ||
|
|
6a039cc828 | ||
|
|
2656f4e108 | ||
|
|
e1be8325cd | ||
|
|
26807aaf05 | ||
|
|
a9d07b742d | ||
|
|
0c3e710069 | ||
|
|
a067c3d5ab | ||
|
|
3fca186a7b | ||
|
|
a8b9742ecd | ||
|
|
31cd755121 | ||
|
|
679222c549 | ||
|
|
29899d84e8 | ||
|
|
7d26e264b9 | ||
|
|
bac0f97d2f | ||
|
|
c4e44617e2 | ||
|
|
25e14f52b8 | ||
|
|
3a40ffcf5f | ||
|
|
3277792c11 | ||
|
|
ac2e8a74d0 | ||
|
|
8107d46ab7 | ||
|
|
a4e8475a2f | ||
|
|
f02586c1c9 | ||
|
|
31ae95179d | ||
|
|
cd86c7302b | ||
|
|
21831e61ad | ||
|
|
7c1cba9a39 | ||
|
|
29e179f50b | ||
|
|
2fa69494ad | ||
|
|
4e717f6c82 | ||
|
|
173716a8a9 | ||
|
|
3c915726f4 | ||
|
|
5cfdb7ee17 | ||
|
|
51648a5093 | ||
|
|
2297daa673 | ||
|
|
7711f454ba | ||
|
|
b518514f36 | ||
|
|
7e50ed34dd | ||
|
|
c85e37ca1f | ||
|
|
58f580c801 | ||
|
|
4ba71aefa9 | ||
|
|
bed834cb73 | ||
|
|
eb51009c09 | ||
|
|
da74d9ff8c | ||
|
|
c8a0e2865a | ||
|
|
627cf8f914 | ||
|
|
e9b4db003e | ||
|
|
45a1728dfb | ||
|
|
f854b54cb0 | ||
|
|
6948da8f6a | ||
|
|
922dcd8b1a | ||
|
|
c8909e4d5e | ||
|
|
060d1856e7 | ||
|
|
6bacc4484e | ||
|
|
892b3b8279 | ||
|
|
00afc45f14 | ||
|
|
868c383d7f | ||
|
|
54d8d414ea | ||
|
|
057deedeb0 | ||
|
|
d64e5a8b19 | ||
|
|
711a3ee644 | ||
|
|
39da97ffa3 | ||
|
|
1473c8f431 | ||
|
|
c210544758 | ||
|
|
f863c717a4 | ||
|
|
5423e61ad7 | ||
|
|
377244a880 | ||
|
|
6f1c3ded0c | ||
|
|
b181c9cdbe | ||
|
|
d5a5335100 | ||
|
|
c937b9ed6c | ||
|
|
56b8c04f36 | ||
|
|
a18b837919 | ||
|
|
033a0990cb | ||
|
|
24fe88806a | ||
|
|
e2f2badc29 | ||
|
|
3ccd77fd44 | ||
|
|
686f633329 | ||
|
|
28154ea36e | ||
|
|
2ca91788af | ||
|
|
2d11601618 | ||
|
|
545600f75b | ||
|
|
d6c937fe37 | ||
|
|
d105f8c891 | ||
|
|
048c75a66e | ||
|
|
a08a398026 | ||
|
|
311b685893 | ||
|
|
3d7f5a853b | ||
|
|
b846d206bc | ||
|
|
2bcc487a09 | ||
|
|
ae2ae7470f | ||
|
|
02ff2d03e1 | ||
|
|
f8bd2b7266 | ||
|
|
d1b98bf91e | ||
|
|
fd4bc863ba | ||
|
|
98f19717b4 | ||
|
|
8f88c85bbf | ||
|
|
7a50426e16 | ||
|
|
503c7cb617 |
21
.editorconfig
Normal file
21
.editorconfig
Normal file
@@ -0,0 +1,21 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
max_line_length = 100
|
||||
tab_width = 2
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
max_line_length = off
|
||||
|
||||
[COMMIT_EDITMSG]
|
||||
max_line_length = off
|
||||
2
.eslintignore
Normal file
2
.eslintignore
Normal file
@@ -0,0 +1,2 @@
|
||||
**/node_modules/**
|
||||
**/action/**
|
||||
22
.eslintrc.json
Normal file
22
.eslintrc.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"parser": "babel-eslint",
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true,
|
||||
"jest": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"ecmaFeatures": {
|
||||
"impliedStrict": true
|
||||
}
|
||||
},
|
||||
"extends": ["airbnb", "plugin:unicorn/recommended", "prettier"],
|
||||
"plugins": ["react", "jsx-a11y", "import", "prettier", "flowtype", "unicorn"],
|
||||
"settings": { "react": { "version": "latest" } },
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"import/no-extraneous-dependencies": 0,
|
||||
"no-underscore-dangle": 0
|
||||
}
|
||||
}
|
||||
9
.github/pull_request_template.md
vendored
Normal file
9
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
#### Changes
|
||||
|
||||
- ...
|
||||
|
||||
#### Checklist
|
||||
|
||||
- [x] Read the contribution [guide](../CONTRIBUTING.md) and accept the [code](../CODE_OF_CONDUCT.md) of conduct
|
||||
- [ ] Readme (updated or not needed)
|
||||
- [ ] Tests (added, updated or not needed)
|
||||
16
.github/workflows/cats.yml
vendored
Normal file
16
.github/workflows/cats.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Cats 😺
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
|
||||
jobs:
|
||||
aCatForCreatingThePullRequest:
|
||||
name: A cat for your effort!
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: ruairidhwm/action-cats@1.0.1
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
240
.github/workflows/main.yml
vendored
240
.github/workflows/main.yml
vendored
@@ -1,111 +1,265 @@
|
||||
name: Actions 😎
|
||||
on: [push]
|
||||
on:
|
||||
push: { branches: [main] }
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
|
||||
env:
|
||||
UNITY_LICENSE: "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root>\n <License id=\"Terms\">\n <MachineBindings>\n <Binding Key=\"1\" Value=\"576562626572264761624c65526f7578\"/>\n <Binding Key=\"2\" Value=\"576562626572264761624c65526f7578\"/>\n </MachineBindings>\n <MachineID Value=\"D7nTUnjNAmtsUMcnoyrqkgIbYdM=\"/>\n <SerialHash Value=\"2033b8ac3e6faa3742ca9f0bfae44d18f2a96b80\"/>\n <Features>\n <Feature Value=\"33\"/>\n <Feature Value=\"1\"/>\n <Feature Value=\"12\"/>\n <Feature Value=\"2\"/>\n <Feature Value=\"24\"/>\n <Feature Value=\"3\"/>\n <Feature Value=\"36\"/>\n <Feature Value=\"17\"/>\n <Feature Value=\"19\"/>\n <Feature Value=\"62\"/>\n </Features>\n <DeveloperData Value=\"AQAAAEY0LUJHUlgtWEQ0RS1aQ1dWLUM1SlctR0RIQg==\"/>\n <SerialMasked Value=\"F4-BGRX-XD4E-ZCWV-C5JW-XXXX\"/>\n <StartDate Value=\"2021-02-08T00:00:00\"/>\n <UpdateDate Value=\"2021-02-09T00:34:57\"/>\n <InitialActivationDate Value=\"2021-02-08T00:34:56\"/>\n <LicenseVersion Value=\"6.x\"/>\n <ClientProvidedVersion Value=\"2018.4.30f1\"/>\n <AlwaysOnline Value=\"false\"/>\n <Entitlements>\n <Entitlement Ns=\"unity_editor\" Tag=\"UnityPersonal\" Type=\"EDITOR\" ValidTo=\"9999-12-31T00:00:00\"/>\n <Entitlement Ns=\"unity_editor\" Tag=\"DarkSkin\" Type=\"EDITOR_FEATURE\" ValidTo=\"9999-12-31T00:00:00\"/>\n </Entitlements>\n </License>\n<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><SignedInfo><CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments\"/><SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/><Reference URI=\"#Terms\"><Transforms><Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/></Transforms><DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><DigestValue>m0Db8UK+ktnOLJBtHybkfetpcKo=</DigestValue></Reference></SignedInfo><SignatureValue>o/pUbSQAukz7+ZYAWhnA0AJbIlyyCPL7bKVEM2lVqbrXt7cyey+umkCXamuOgsWPVUKBMkXtMH8L\n5etLmD0getWIhTGhzOnDCk+gtIPfL4jMo9tkEuOCROQAXCci23VFscKcrkB+3X6h4wEOtA2APhOY\nB+wvC794o8/82ffjP79aVAi57rp3Wmzx+9pe9yMwoJuljAy2sc2tIMgdQGWVmOGBpQm3JqsidyzI\nJWG2kjnc7pDXK9pwYzXoKiqUqqrut90d+kQqRyv7MSZXR50HFqD/LI69h68b7P8Bjo3bPXOhNXGR\n9YCoemH6EkfCJxp2gIjzjWW+l2Hj2EsFQi8YXw==</SignatureValue></Signature></root>"
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 12.x
|
||||
- run: yarn
|
||||
- run: yarn lint
|
||||
- run: yarn test
|
||||
- run: yarn build || { echo "build command should always succeed" ; exit 61; }
|
||||
- run: yarn build --quiet && git diff --quiet action || { echo "ERROR - index.js is different from repository version. Forgot to run `yarn build`?" ; exit 62; }
|
||||
|
||||
testAllModesLikeInTheReadme:
|
||||
name: Test in ${{ matrix.testMode }} on version ${{ matrix.unityVersion }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
projectPath:
|
||||
- unity-project-with-correct-tests
|
||||
unityVersion:
|
||||
- 2019.2.11f1
|
||||
testMode:
|
||||
- playmode
|
||||
- editmode
|
||||
steps:
|
||||
###########################
|
||||
# Checkout #
|
||||
###########################
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
###########################
|
||||
# Cache #
|
||||
###########################
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ matrix.projectPath }}/Library
|
||||
key: Library-${{ matrix.projectPath }}
|
||||
restore-keys: |
|
||||
Library-
|
||||
- uses: ./
|
||||
id: tests
|
||||
with:
|
||||
projectPath: ${{ matrix.projectPath }}
|
||||
unityVersion: ${{ matrix.unityVersion }}
|
||||
testMode: ${{ matrix.testMode }}
|
||||
artifactsPath: ${{ matrix.testMode }}-artifacts
|
||||
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Test results for ${{ matrix.testMode }}
|
||||
path: ${{ steps.tests.outputs.artifactsPath }}
|
||||
retention-days: 14
|
||||
|
||||
testRunnerInAllModes:
|
||||
name: Test all modes ✨
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
projectPath:
|
||||
- unity-project-with-correct-tests
|
||||
unityVersion:
|
||||
- 2019.2.11f1
|
||||
steps:
|
||||
# Checkout repository (required to test local actions)
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v1
|
||||
###########################
|
||||
# Checkout #
|
||||
###########################
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
###########################
|
||||
# Cache #
|
||||
###########################
|
||||
- uses: actions/cache@v1.1.0
|
||||
with:
|
||||
path: ${{ matrix.projectPath }}/Library
|
||||
key: Library-${{ matrix.projectPath }}-${{ matrix.targetPlatform }}
|
||||
restore-keys: |
|
||||
Library-${{ matrix.projectPath }}-
|
||||
Library-
|
||||
|
||||
# Configure test runner
|
||||
- name: Run tests
|
||||
id: allTests
|
||||
uses: ./
|
||||
env:
|
||||
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
||||
TEST_MODE: all
|
||||
UNITY_PROJECT_PATH: unity-project-with-correct-tests
|
||||
# Test implicit ARTIFACTS_PATH, by not setting it
|
||||
with:
|
||||
projectPath: ${{ matrix.projectPath }}
|
||||
unityVersion: ${{ matrix.unityVersion }}
|
||||
testMode: all
|
||||
# Test implicit artifactsPath, by not setting it
|
||||
|
||||
# Upload artifacts
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Test results (all)
|
||||
path: ${{ steps.allTests.outputs.artifactsPath }}
|
||||
retention-days: 14
|
||||
|
||||
testRunnerInEditMode:
|
||||
name: Test edit mode 📝
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
unityVersion:
|
||||
- 2019.2.11f1
|
||||
projectPath:
|
||||
- unity-project-with-correct-tests
|
||||
steps:
|
||||
# Checkout repository (required to test local actions)
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v1
|
||||
###########################
|
||||
# Checkout #
|
||||
###########################
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
###########################
|
||||
# Cache #
|
||||
###########################
|
||||
- uses: actions/cache@v1.1.0
|
||||
with:
|
||||
path: ${{ matrix.projectPath }}/Library
|
||||
key: Library-${{ matrix.projectPath }}-${{ matrix.targetPlatform }}
|
||||
restore-keys: |
|
||||
Library-${{ matrix.projectPath }}-
|
||||
Library-
|
||||
|
||||
# Configure test runner
|
||||
- name: Run tests
|
||||
id: editMode
|
||||
uses: ./
|
||||
env:
|
||||
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
||||
TEST_MODE: editmode
|
||||
UNITY_PROJECT_PATH: unity-project-with-correct-tests
|
||||
ARTIFACTS_PATH: artifacts/editmode
|
||||
with:
|
||||
projectPath: ${{ matrix.projectPath }}
|
||||
unityVersion: ${{ matrix.unityVersion }}
|
||||
testMode: editmode
|
||||
artifactsPath: artifacts/editmode
|
||||
|
||||
# Upload artifacts
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Test results (edit mode)
|
||||
path: ${{ steps.editMode.outputs.artifactsPath }}
|
||||
retention-days: 14
|
||||
|
||||
testRunnerInPlayMode:
|
||||
name: Test play mode 📺
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
projectPath:
|
||||
- unity-project-with-correct-tests
|
||||
unityVersion:
|
||||
- 2019.2.11f1
|
||||
steps:
|
||||
# Checkout repository (required to test local actions)
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v1
|
||||
###########################
|
||||
# Checkout #
|
||||
###########################
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
###########################
|
||||
# Cache #
|
||||
###########################
|
||||
- uses: actions/cache@v1.1.0
|
||||
with:
|
||||
path: ${{ matrix.projectPath }}/Library
|
||||
key: Library-${{ matrix.projectPath }}-${{ matrix.targetPlatform }}
|
||||
restore-keys: |
|
||||
Library-${{ matrix.projectPath }}-
|
||||
Library-
|
||||
|
||||
# Configure test runner
|
||||
- name: Run tests
|
||||
id: playMode
|
||||
uses: ./
|
||||
env:
|
||||
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
||||
TEST_MODE: playmode
|
||||
UNITY_PROJECT_PATH: unity-project-with-correct-tests
|
||||
ARTIFACTS_PATH: artifacts/editmode
|
||||
with:
|
||||
projectPath: ${{ matrix.projectPath }}
|
||||
unityVersion: ${{ matrix.unityVersion }}
|
||||
testMode: playmode
|
||||
artifactsPath: artifacts/playmode
|
||||
|
||||
# Upload artifacts
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Test results (play mode)
|
||||
path: ${{ steps.playMode.outputs.artifactsPath }}
|
||||
retention-days: 14
|
||||
|
||||
testRunnerInEachModeSeparately:
|
||||
testEachModeSequentially:
|
||||
name: Test each mode sequentially 👩👩👧👦 # don't try this at home (it's much slower)
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
unityVersion:
|
||||
- 2019.2.11f1
|
||||
projectPath:
|
||||
- unity-project-with-correct-tests
|
||||
steps:
|
||||
# Checkout repository (required to test local actions)
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v1
|
||||
###########################
|
||||
# Checkout #
|
||||
###########################
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
###########################
|
||||
# Cache #
|
||||
###########################
|
||||
- uses: actions/cache@v1.1.0
|
||||
with:
|
||||
path: ${{ matrix.projectPath }}/Library
|
||||
key: Library-${{ matrix.projectPath }}-${{ matrix.targetPlatform }}
|
||||
restore-keys: |
|
||||
Library-${{ matrix.projectPath }}-
|
||||
Library-
|
||||
|
||||
# Configure first test runner
|
||||
- name: Tests in editmode 📝
|
||||
uses: ./
|
||||
env:
|
||||
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
||||
TEST_MODE: editmode
|
||||
UNITY_PROJECT_PATH: unity-project-with-correct-tests
|
||||
ARTIFACTS_PATH: artifacts/editmode
|
||||
with:
|
||||
projectPath: ${{ matrix.projectPath }}
|
||||
unityVersion: ${{ matrix.unityVersion }}
|
||||
testMode: editmode
|
||||
artifactsPath: artifacts/editmode
|
||||
|
||||
# Configure second test runner
|
||||
- name: Tests in playmode 📺
|
||||
uses: ./
|
||||
env:
|
||||
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
||||
TEST_MODE: playmode
|
||||
UNITY_PROJECT_PATH: unity-project-with-correct-tests
|
||||
ARTIFACTS_PATH: artifacts/playmode
|
||||
with:
|
||||
projectPath: ${{ matrix.projectPath }}
|
||||
unityVersion: ${{ matrix.unityVersion }}
|
||||
testMode: playmode
|
||||
artifactsPath: artifacts/playmode
|
||||
|
||||
# Upload combined artifacts
|
||||
- name: Upload combined test results
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Test results (combined)
|
||||
path: artifacts/
|
||||
retention-days: 14
|
||||
|
||||
14
.github/workflows/versioning.yml
vendored
Normal file
14
.github/workflows/versioning.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: Versioning
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published, edited]
|
||||
|
||||
jobs:
|
||||
updateMajorTag:
|
||||
name: Update major tag
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: Actions-R-Us/actions-tagger@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
.idea
|
||||
node_modules
|
||||
|
||||
2
.prettierignore
Normal file
2
.prettierignore
Normal file
@@ -0,0 +1,2 @@
|
||||
**/node_modules/**
|
||||
**/action/**
|
||||
7
.prettierrc.json
Normal file
7
.prettierrc.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 100,
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
3
.yarnrc
Normal file
3
.yarnrc
Normal file
@@ -0,0 +1,3 @@
|
||||
save-prefix ""
|
||||
--install.audit true
|
||||
--add.audit true
|
||||
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at webber@takken.io. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
39
CONTRIBUTING.md
Normal file
39
CONTRIBUTING.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Contributing
|
||||
|
||||
## How to Contribute
|
||||
|
||||
#### Code of Conduct
|
||||
|
||||
This repository has adopted the Contributor Covenant as it's
|
||||
Code of Conduct. It is expected that participants adhere to it.
|
||||
|
||||
#### Proposing a Change
|
||||
|
||||
If you are unsure about whether or not a change is desired,
|
||||
you can create an issue. This is useful because it creates
|
||||
the possibility for a discussion that's visible to everyone.
|
||||
|
||||
When fixing a bug it is fine to submit a pull request right away.
|
||||
|
||||
#### Sending a Pull Request
|
||||
|
||||
Steps to be performed to submit a pull request:
|
||||
|
||||
1. Fork the repository and create your branch from `master`.
|
||||
2. Run `yarn` in the repository root.
|
||||
3. If you've fixed a bug or added code that should be tested, add tests!
|
||||
4. Fill out the description, link any related issues and submit your pull request.
|
||||
|
||||
#### Pull Request Prerequisites
|
||||
|
||||
You have [Node](https://nodejs.org/) installed at v12.2.0+ and [Yarn](https://yarnpkg.com/) at v1.18.0+.
|
||||
|
||||
Please note that commit hooks will run automatically to perform some tasks;
|
||||
|
||||
- format your code
|
||||
- run tests & lint - `yarn lint && yarn test`
|
||||
- build distributable files - `yarn build`
|
||||
|
||||
#### License
|
||||
|
||||
By contributing to this repository, you agree that your contributions will be licensed under its MIT license.
|
||||
78
README.md
78
README.md
@@ -1,66 +1,44 @@
|
||||
# Unity - Test runner
|
||||
[](https://github.com/WebberTakken/unity-test-runner/workflows/Actions%20%F0%9F%98%8E/badge.svg)
|
||||
|
||||
[Github Action](https://github.com/features/actions)
|
||||
to run tests for any Unity project.
|
||||
(Not affiliated with Unity Technologies)
|
||||
|
||||
This is a recommended step to prepare your pipeline for using the
|
||||
[Build](https://github.com/webbertakken/unity-actions#build)
|
||||
action from the
|
||||
[Unity Actions](https://github.com/webbertakken/unity-actions)
|
||||
collection.
|
||||
GitHub Action to
|
||||
[run tests](https://github.com/marketplace/actions/unity-test-runner)
|
||||
for any Unity project.
|
||||
|
||||
Requires the [activation](https://github.com/marketplace/actions/unity-activate) step.
|
||||
Part of the <a href="https://game.ci">GameCI</a> open source project.
|
||||
<br />
|
||||
<br />
|
||||
|
||||
## Usage
|
||||
[](https://github.com/game-ci/unity-test-runner/actions?query=workflow%3A%22Actions+%F0%9F%98%8E%22)
|
||||
<br />
|
||||
<br />
|
||||
|
||||
Create or edit the file called `.github/workflows/activation.yml` and add a job to it.
|
||||
## How to use
|
||||
|
||||
```yaml
|
||||
name: Test project
|
||||
on: [push]
|
||||
jobs:
|
||||
testRunnerInAllModes:
|
||||
name: Test all modes ✨
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
```
|
||||
Find the
|
||||
[docs](https://game.ci/docs/github/test-runner)
|
||||
on the GameCI
|
||||
[documentation website](https://game.ci/docs).
|
||||
|
||||
Configure the test runner as follows:
|
||||
## Related actions
|
||||
|
||||
```yaml
|
||||
# Configure test runner
|
||||
- name: Run tests
|
||||
id: myTestStep
|
||||
uses: webbertakken/unity-test-runner@v1
|
||||
env:
|
||||
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
||||
Visit the
|
||||
GameCI <a href="https://github.com/game-ci/unity-actions">Unity Actions</a>
|
||||
status repository for related Actions.
|
||||
|
||||
# Choose: "all", "playmode", "editmode"
|
||||
TEST_MODE: all
|
||||
|
||||
# Optional: Path to your project, leave blank for "./"
|
||||
UNITY_PROJECT_PATH: relative/path/to/your/project
|
||||
## Community
|
||||
|
||||
# Optional: Artifacts path, leave blank for "artifacts"
|
||||
ARTIFACTS_PATH: store/artifacts/here
|
||||
```
|
||||
Feel free to join us on
|
||||
<a href="http://game.ci/discord"><img height="30" src="media/Discord-Logo.svg" alt="Discord" /></a>
|
||||
and engage with the community.
|
||||
|
||||
You use the id to **upload the artifacts** like so:
|
||||
## Contributing
|
||||
|
||||
```yaml
|
||||
# Upload artifacts
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: Test results
|
||||
path: ${{ steps.myTestStep.outputs.artifactsPath }}
|
||||
```
|
||||
To help improve the documentation, please find the docs [repository](https://github.com/game-ci/documentation).
|
||||
|
||||
Commit and push your workflow definition.
|
||||
To contribute to this project, kindly read the [contribution guide](./CONTRIBUTING.md).
|
||||
|
||||
## More actions
|
||||
## Licence
|
||||
|
||||
Visit
|
||||
[Unity Actions](https://github.com/webbertakken/unity-actions)
|
||||
to find related actions for Unity.
|
||||
[MIT](./LICENSE)
|
||||
|
||||
42
action.yml
42
action.yml
@@ -1,13 +1,47 @@
|
||||
name: 'Unity - Test runner'
|
||||
author: Webber Takken <webber@takken.io>
|
||||
description: 'Run tests for any Unity project.'
|
||||
inputs: {}
|
||||
inputs:
|
||||
unityVersion:
|
||||
required: false
|
||||
default: 'auto'
|
||||
description: 'Version of unity to use for testing the project. Use "auto" to get from your ProjectSettings/ProjectVersion.txt'
|
||||
customImage:
|
||||
required: false
|
||||
default: ''
|
||||
description: 'Specific docker image that should be used for testing the project'
|
||||
projectPath:
|
||||
required: false
|
||||
description: 'Path to the Unity project to be tested.'
|
||||
testMode:
|
||||
required: false
|
||||
default: 'all'
|
||||
description: 'The type of tests to be run by the test runner.'
|
||||
artifactsPath:
|
||||
required: false
|
||||
default: 'artifacts'
|
||||
description: 'Path where test artifacts should be stored.'
|
||||
useNetworkHost:
|
||||
required: false
|
||||
default: false
|
||||
description: 'Initialises Docker using the hosts network.'
|
||||
customParameters:
|
||||
required: false
|
||||
description: 'Extra parameters to configure the Unity editor run.'
|
||||
githubToken:
|
||||
required: false
|
||||
default: ''
|
||||
description: 'Token to authorize access to the GitHub REST API. If provided, a check run will be created with the test results.'
|
||||
checkName:
|
||||
required: false
|
||||
default: 'Test Results'
|
||||
description: 'Name for the check run that is created when a github token is provided.'
|
||||
outputs:
|
||||
artifactsPath:
|
||||
description: 'Path where the artifacts are stored'
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
||||
branding:
|
||||
icon: 'box'
|
||||
color: 'gray-dark'
|
||||
runs:
|
||||
using: 'node12'
|
||||
main: 'action/index.js'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
FROM gableroux/unity3d:2019.2.11f1
|
||||
ARG IMAGE
|
||||
FROM $IMAGE
|
||||
|
||||
LABEL "com.github.actions.name"="Unity - Test runner"
|
||||
LABEL "com.github.actions.description"="Run tests for any Unity project."
|
||||
@@ -9,6 +10,8 @@ LABEL "repository"="http://github.com/webbertakken/unity-actions"
|
||||
LABEL "homepage"="http://github.com/webbertakken/unity-actions"
|
||||
LABEL "maintainer"="Webber Takken <webber@takken.io>"
|
||||
|
||||
ADD steps /steps
|
||||
RUN chmod -R +x /steps
|
||||
ADD entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
47
action/entrypoint.sh
Normal file
47
action/entrypoint.sh
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#
|
||||
# Create directory for license activation
|
||||
#
|
||||
|
||||
ACTIVATE_LICENSE_PATH="$GITHUB_WORKSPACE/_activate-license"
|
||||
mkdir -p "$ACTIVATE_LICENSE_PATH"
|
||||
|
||||
#
|
||||
# Run steps
|
||||
#
|
||||
|
||||
source /steps/activate.sh
|
||||
source /steps/run_tests.sh
|
||||
source /steps/return_license.sh
|
||||
|
||||
#
|
||||
# Remove license activation directory
|
||||
#
|
||||
|
||||
rm -r "$ACTIVATE_LICENSE_PATH"
|
||||
|
||||
#
|
||||
# Instructions for debugging
|
||||
#
|
||||
|
||||
if [[ $TEST_RUNNER_EXIT_CODE -gt 0 ]]; then
|
||||
echo ""
|
||||
echo "###########################"
|
||||
echo "# Failure #"
|
||||
echo "###########################"
|
||||
echo ""
|
||||
echo "Please note that the exit code is not very descriptive."
|
||||
echo "Most likely it will not help you solve the issue."
|
||||
echo ""
|
||||
echo "To find the reason for failure: please search for errors in the log above."
|
||||
echo ""
|
||||
fi;
|
||||
|
||||
#
|
||||
# Exit with code from the build step.
|
||||
#
|
||||
|
||||
if [ $USE_EXIT_CODE = true ]; then
|
||||
exit $TEST_RUNNER_EXIT_CODE
|
||||
fi;
|
||||
1
action/index.js
Normal file
1
action/index.js
Normal file
File diff suppressed because one or more lines are too long
18
action/results-check-details.hbs
Normal file
18
action/results-check-details.hbs
Normal file
@@ -0,0 +1,18 @@
|
||||
{{#runs}}
|
||||
|
||||
<details><summary>{{summary}}</summary>
|
||||
|
||||
{{#suites}}
|
||||
* {{summary}}
|
||||
{{#tests}}
|
||||
* {{summary}}
|
||||
{{#if annotation}}
|
||||
{{indent annotation.message}}
|
||||
{{indent annotation.raw_details}}
|
||||
{{/if}}
|
||||
{{/tests}}
|
||||
{{/suites}}
|
||||
|
||||
</details>
|
||||
|
||||
{{/runs}}
|
||||
3
action/results-check-summary.hbs
Normal file
3
action/results-check-summary.hbs
Normal file
@@ -0,0 +1,3 @@
|
||||
{{#runs}}
|
||||
### {{summary}}
|
||||
{{/runs}}
|
||||
111
action/steps/activate.sh
Normal file
111
action/steps/activate.sh
Normal file
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Run in ACTIVATE_LICENSE_PATH directory
|
||||
echo "Changing to \"$ACTIVATE_LICENSE_PATH\" directory."
|
||||
pushd "$ACTIVATE_LICENSE_PATH"
|
||||
|
||||
if [[ -n "$UNITY_LICENSE" ]] || [[ -n "$UNITY_LICENSE_FILE" ]]; then
|
||||
#
|
||||
# PERSONAL LICENSE MODE
|
||||
#
|
||||
# This will activate Unity, using a license file
|
||||
#
|
||||
# Note that this is the ONLY WAY for PERSONAL LICENSES in 2020.
|
||||
# * See for more details: https://gitlab.com/gableroux/unity3d-gitlab-ci-example/issues/5#note_72815478
|
||||
#
|
||||
# The license file can be acquired using `game-ci/request-manual-activation-file` action.
|
||||
echo "Requesting activation (personal license)"
|
||||
|
||||
# Set the license file path
|
||||
FILE_PATH=UnityLicenseFile.ulf
|
||||
|
||||
if [[ -n "$UNITY_LICENSE" ]]; then
|
||||
# Copy license file from Github variables
|
||||
echo "$UNITY_LICENSE" | tr -d '\r' > $FILE_PATH
|
||||
elif [[ -n "$UNITY_LICENSE_FILE" ]]; then
|
||||
# Copy license file from file system
|
||||
cat "$UNITY_LICENSE_FILE" | tr -d '\r' > $FILE_PATH
|
||||
fi
|
||||
|
||||
# Activate license
|
||||
ACTIVATION_OUTPUT=$(unity-editor \
|
||||
-batchmode \
|
||||
-nographics \
|
||||
-logFile /dev/stdout \
|
||||
-quit \
|
||||
-manualLicenseFile $FILE_PATH)
|
||||
|
||||
# Store the exit code from the verify command
|
||||
UNITY_EXIT_CODE=$?
|
||||
|
||||
# The exit code for personal activation is always 1;
|
||||
# Determine whether activation was successful.
|
||||
#
|
||||
# Successful output should include the following:
|
||||
#
|
||||
# "LICENSE SYSTEM [2020120 18:51:20] Next license update check is after 2019-11-25T18:23:38"
|
||||
#
|
||||
ACTIVATION_SUCCESSFUL=$(echo $ACTIVATION_OUTPUT | grep 'Next license update check is after' | wc -l)
|
||||
|
||||
# Set exit code to 0 if activation was successful
|
||||
if [[ $ACTIVATION_SUCCESSFUL -eq 1 ]]; then
|
||||
UNITY_EXIT_CODE=0
|
||||
fi;
|
||||
|
||||
# Remove license file
|
||||
rm -f $FILE_PATH
|
||||
|
||||
elif [[ -n "$UNITY_SERIAL" && -n "$UNITY_EMAIL" && -n "$UNITY_PASSWORD" ]]; then
|
||||
#
|
||||
# PROFESSIONAL (SERIAL) LICENSE MODE
|
||||
#
|
||||
# This will activate unity, using the activating process.
|
||||
#
|
||||
# Note: This is the preferred way for PROFESSIONAL LICENSES.
|
||||
#
|
||||
echo "Requesting activation (professional license)"
|
||||
|
||||
# Activate license
|
||||
unity-editor \
|
||||
-batchmode \
|
||||
-nographics \
|
||||
-logFile /dev/stdout \
|
||||
-quit \
|
||||
-serial "$UNITY_SERIAL" \
|
||||
-username "$UNITY_EMAIL" \
|
||||
-password "$UNITY_PASSWORD"
|
||||
|
||||
# Store the exit code from the verify command
|
||||
UNITY_EXIT_CODE=$?
|
||||
|
||||
else
|
||||
#
|
||||
# NO LICENSE ACTIVATION STRATEGY MATCHED
|
||||
#
|
||||
# This will exit since no activation strategies could be matched.
|
||||
#
|
||||
echo "License activation strategy could not be determined."
|
||||
echo ""
|
||||
echo "Visit https://github.com/webbertakken/unity-builder#usage for more"
|
||||
echo "details on how to set up one of the possible activation strategies."
|
||||
|
||||
# Immediately exit as no UNITY_EXIT_CODE can be derived.
|
||||
exit 1;
|
||||
|
||||
fi
|
||||
|
||||
#
|
||||
# Display information about the result
|
||||
#
|
||||
if [ $UNITY_EXIT_CODE -eq 0 ]; then
|
||||
# Activation was a success
|
||||
echo "Activation complete."
|
||||
else
|
||||
# Activation failed so exit with the code from the license verification step
|
||||
echo "Unclassified error occured while trying to activate license."
|
||||
echo "Exit code was: $UNITY_EXIT_CODE"
|
||||
exit $UNITY_EXIT_CODE
|
||||
fi
|
||||
|
||||
# Return to previous working directory
|
||||
popd
|
||||
22
action/steps/return_license.sh
Normal file
22
action/steps/return_license.sh
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Run in ACTIVATE_LICENSE_PATH directory
|
||||
echo "Changing to \"$ACTIVATE_LICENSE_PATH\" directory."
|
||||
pushd "$ACTIVATE_LICENSE_PATH"
|
||||
|
||||
if [[ -n "$UNITY_SERIAL" ]]; then
|
||||
#
|
||||
# PROFESSIONAL (SERIAL) LICENSE MODE
|
||||
#
|
||||
# This will return the license that is currently in use.
|
||||
#
|
||||
unity-editor \
|
||||
-batchmode \
|
||||
-nographics \
|
||||
-logFile /dev/stdout \
|
||||
-quit \
|
||||
-returnlicense
|
||||
fi
|
||||
|
||||
# Return to previous working directory
|
||||
popd
|
||||
@@ -1,15 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Set the license file path
|
||||
LICENSE_FILE_PATH=UnityLicenseFile.ulf
|
||||
UNITY_PROJECT_PATH=$GITHUB_WORKSPACE/$UNITY_PROJECT_PATH
|
||||
#
|
||||
# Set and display project path
|
||||
#
|
||||
|
||||
# Set the artifacts path
|
||||
if [ -z "$ARTIFACTS_PATH" ]; then
|
||||
ARTIFACTS_PATH=artifacts
|
||||
fi
|
||||
UNITY_PROJECT_PATH="$GITHUB_WORKSPACE/$PROJECT_PATH"
|
||||
echo "Using project path \"$UNITY_PROJECT_PATH\"."
|
||||
|
||||
#
|
||||
# Set and display the artifacts path
|
||||
#
|
||||
|
||||
echo "Using artifacts path \"$ARTIFACTS_PATH\" to save test results."
|
||||
FULL_ARTIFACTS_PATH=$GITHUB_WORKSPACE/$ARTIFACTS_PATH
|
||||
|
||||
#
|
||||
# Display custom parameters
|
||||
#
|
||||
echo "Using custom parameters $CUSTOM_PARAMETERS."
|
||||
|
||||
# Set the modes for testing
|
||||
case $TEST_MODE in
|
||||
editmode)
|
||||
@@ -27,32 +36,15 @@ case $TEST_MODE in
|
||||
;;
|
||||
esac
|
||||
|
||||
# Copy license file from Github variables
|
||||
echo "$UNITY_LICENSE" | tr -d '\r' > $LICENSE_FILE_PATH
|
||||
echo "$UNITY_LICENSE" | tr -d '\r' > /root/.local/share/unity3d/Unity/Unity_lic.ulf
|
||||
# TODO - test if this line has any effect
|
||||
echo "$UNITY_LICENSE" | tr -d '\r' > /root/.local/share/unity3d/Unity/Unity_v2019.x.ulf
|
||||
|
||||
##
|
||||
## Activate license
|
||||
##
|
||||
echo "Requesting activation"
|
||||
xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' \
|
||||
/opt/Unity/Editor/Unity \
|
||||
-batchmode \
|
||||
-nographics \
|
||||
-logFile /dev/stdout \
|
||||
-quit \
|
||||
-manualLicenseFile $LICENSE_FILE_PATH
|
||||
# This is expected to always exit with code 1 (both success and failure).
|
||||
# Convert to exit code 0 by echoing the current exit code.
|
||||
echo $?
|
||||
# Exit code is now 0
|
||||
|
||||
|
||||
# The following tests are 2019 mode (requires Unity 2019.2.11f1 or later)
|
||||
# Reference: https://docs.unity3d.com/2019.3/Documentation/Manual/CommandLineArguments.html
|
||||
|
||||
#
|
||||
# Display the unity version
|
||||
#
|
||||
|
||||
echo "Using Unity version \"$UNITY_VERSION\" to test."
|
||||
|
||||
#
|
||||
# Overall info
|
||||
#
|
||||
@@ -82,18 +74,21 @@ if [ $EDIT_MODE = true ]; then
|
||||
echo "# Testing in EditMode #"
|
||||
echo "###########################"
|
||||
echo ""
|
||||
xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' \
|
||||
/opt/Unity/Editor/Unity \
|
||||
-batchmode \
|
||||
-logfile /dev/stdout \
|
||||
-projectPath "$UNITY_PROJECT_PATH" \
|
||||
-runTests \
|
||||
-testPlatform editmode \
|
||||
-testResults "$FULL_ARTIFACTS_PATH/editmode-results.xml"
|
||||
unity-editor \
|
||||
-batchmode \
|
||||
-logFile "$FULL_ARTIFACTS_PATH/editmode.log" \
|
||||
-projectPath "$UNITY_PROJECT_PATH" \
|
||||
-runTests \
|
||||
-testPlatform editmode \
|
||||
-testResults "$FULL_ARTIFACTS_PATH/editmode-results.xml" \
|
||||
$CUSTOM_PARAMETERS
|
||||
|
||||
# Catch exit code
|
||||
EDIT_MODE_EXIT_CODE=$?
|
||||
|
||||
# Print unity log output
|
||||
cat "$FULL_ARTIFACTS_PATH/editmode.log"
|
||||
|
||||
# Display results
|
||||
if [ $EDIT_MODE_EXIT_CODE -eq 0 ]; then
|
||||
echo "Run succeeded, no failures occurred";
|
||||
@@ -116,18 +111,21 @@ if [ $PLAY_MODE = true ]; then
|
||||
echo "# Testing in PlayMode #"
|
||||
echo "###########################"
|
||||
echo ""
|
||||
xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' \
|
||||
/opt/Unity/Editor/Unity \
|
||||
-batchmode \
|
||||
-logfile /dev/stdout \
|
||||
-projectPath "$UNITY_PROJECT_PATH" \
|
||||
-runTests \
|
||||
-testPlatform playmode \
|
||||
-testResults "$FULL_ARTIFACTS_PATH/playmode-results.xml"
|
||||
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";
|
||||
@@ -164,28 +162,21 @@ fi
|
||||
if [ $PLAY_MODE = true ]; then
|
||||
echo ""
|
||||
echo "###########################"
|
||||
echo "# Edit Mode Results #"
|
||||
echo "# Play Mode Results #"
|
||||
echo "###########################"
|
||||
echo ""
|
||||
cat "$FULL_ARTIFACTS_PATH/playmode-results.xml"
|
||||
cat "$FULL_ARTIFACTS_PATH/playmode-results.xml" | grep test-run | grep Passed
|
||||
fi
|
||||
|
||||
#
|
||||
# Output variables
|
||||
#
|
||||
|
||||
# Set resulting name as output variable
|
||||
echo ::set-output name=artifactsPath::$ARTIFACTS_PATH
|
||||
|
||||
#
|
||||
# Exit
|
||||
#
|
||||
|
||||
if [ $EDIT_MODE_EXIT_CODE -gt 0 ]; then
|
||||
exit $EDIT_MODE_EXIT_CODE
|
||||
TEST_RUNNER_EXIT_CODE=$EDIT_MODE_EXIT_CODE
|
||||
fi
|
||||
|
||||
if [ $PLAY_MODE_EXIT_CODE -gt 0 ]; then
|
||||
exit $PLAY_MODE_EXIT_CODE
|
||||
TEST_RUNNER_EXIT_CODE=$PLAY_MODE_EXIT_CODE
|
||||
fi
|
||||
81
artifacts/editmode-results.xml
Normal file
81
artifacts/editmode-results.xml
Normal file
@@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<test-run id="2" testcasecount="6" result="Failed(Child)" total="6" passed="2" failed="2" inconclusive="0" skipped="2" asserts="0" engine-version="3.5.0.0" clr-version="4.0.30319.42000" start-time="2021-01-19 20:51:34Z" end-time="2021-01-19 20:51:34Z" duration="0.1168037">
|
||||
<test-suite type="TestSuite" id="1012" name="sample-project" fullname="sample-project" runstate="Runnable" testcasecount="6" result="Failed" site="Child" start-time="2021-01-19 20:51:34Z" end-time="2021-01-19 20:51:34Z" duration="0.116804" total="6" passed="2" failed="2" inconclusive="0" skipped="2" asserts="0">
|
||||
<properties />
|
||||
<failure>
|
||||
<message><![CDATA[One or more child tests had errors]]></message>
|
||||
</failure>
|
||||
<test-suite type="Assembly" id="1020" name="Editor.dll" fullname="/github/workspace/unity-project/Library/ScriptAssemblies/Editor.dll" runstate="Runnable" testcasecount="6" result="Failed" site="Child" start-time="2021-01-19 20:51:34Z" end-time="2021-01-19 20:51:34Z" duration="0.087946" total="6" passed="2" failed="2" inconclusive="0" skipped="2" asserts="0">
|
||||
<properties>
|
||||
<property name="_PID" value="78" />
|
||||
<property name="_APPDOMAIN" value="Unity Child Domain" />
|
||||
<property name="platform" value="EditMode" />
|
||||
</properties>
|
||||
<failure>
|
||||
<message><![CDATA[One or more child tests had errors]]></message>
|
||||
</failure>
|
||||
<test-suite type="TestSuite" id="1021" name="Editor" fullname="Editor" runstate="Runnable" testcasecount="6" result="Failed" site="Child" start-time="2021-01-19 20:51:34Z" end-time="2021-01-19 20:51:34Z" duration="0.086212" total="6" passed="2" failed="2" inconclusive="0" skipped="2" asserts="0">
|
||||
<properties />
|
||||
<failure>
|
||||
<message><![CDATA[One or more child tests had errors]]></message>
|
||||
</failure>
|
||||
<test-suite type="TestFixture" id="1013" name="EditorModeTest" fullname="Editor.EditorModeTest" classname="Editor.EditorModeTest" runstate="Runnable" testcasecount="6" result="Failed" site="Child" start-time="2021-01-19 20:51:34Z" end-time="2021-01-19 20:51:34Z" duration="0.076416" total="6" passed="2" failed="2" inconclusive="0" skipped="2" asserts="0">
|
||||
<properties />
|
||||
<failure>
|
||||
<message><![CDATA[One or more child tests had errors]]></message>
|
||||
</failure>
|
||||
<output><![CDATA[You are trying to create a MonoBehaviour using the 'new' keyword. This is not allowed. MonoBehaviours can only be added using AddComponent(). Alternatively, your script can inherit from ScriptableObject or no base class at all
|
||||
]]></output>
|
||||
<test-case id="1016" name="FailedTest" fullname="Editor.EditorModeTest.FailedTest" methodname="FailedTest" classname="Editor.EditorModeTest" runstate="Runnable" seed="330279882" result="Failed" start-time="2021-01-19 20:51:34Z" end-time="2021-01-19 20:51:34Z" duration="0.023093" asserts="0">
|
||||
<properties />
|
||||
<failure>
|
||||
<message><![CDATA[ Expected: True
|
||||
But was: False
|
||||
]]></message>
|
||||
<stack-trace><![CDATA[at Editor.EditorModeTest.FailedTest () [0x00000] in /github/workspace/unity-project/Assets/Editor/EditorModeTest.cs:21
|
||||
]]></stack-trace>
|
||||
</failure>
|
||||
</test-case>
|
||||
<test-case id="1019" name="FailedUnityTest" fullname="Editor.EditorModeTest.FailedUnityTest" methodname="FailedUnityTest" classname="Editor.EditorModeTest" runstate="Runnable" seed="347277877" result="Failed" start-time="2021-01-19 20:51:34Z" end-time="2021-01-19 20:51:34Z" duration="0.014203" asserts="0">
|
||||
<properties>
|
||||
<property name="_JOINTYPE" value="UnityCombinatorial" />
|
||||
</properties>
|
||||
<failure>
|
||||
<message><![CDATA[ Expected: True
|
||||
But was: False
|
||||
]]></message>
|
||||
<stack-trace><![CDATA[at Editor.EditorModeTest+<FailedUnityTest>d__5.MoveNext () [0x0002e] in /github/workspace/unity-project/Assets/Editor/EditorModeTest.cs:40
|
||||
at UnityEngine.TestTools.TestEnumerator+<Execute>d__6.MoveNext () [0x00038] in /github/workspace/unity-project/Library/PackageCache/com.unity.test-framework@1.1.19/UnityEngine.TestRunner/NUnitExtensions/Attributes/TestEnumerator.cs:36
|
||||
]]></stack-trace>
|
||||
</failure>
|
||||
</test-case>
|
||||
<test-case id="1015" name="IgnoredTest" fullname="Editor.EditorModeTest.IgnoredTest" methodname="IgnoredTest" classname="Editor.EditorModeTest" runstate="Ignored" seed="1319288303" result="Skipped" label="Ignored" start-time="2021-01-19 20:51:34Z" end-time="2021-01-19 20:51:34Z" duration="0.000218" asserts="0">
|
||||
<properties>
|
||||
<property name="_SKIPREASON" value="ignore" />
|
||||
</properties>
|
||||
<reason>
|
||||
<message><![CDATA[ignore]]></message>
|
||||
</reason>
|
||||
</test-case>
|
||||
<test-case id="1018" name="IgnoredUnityTest" fullname="Editor.EditorModeTest.IgnoredUnityTest" methodname="IgnoredUnityTest" classname="Editor.EditorModeTest" runstate="Ignored" seed="2034877647" result="Skipped" label="Ignored" start-time="2021-01-19 20:51:34Z" end-time="2021-01-19 20:51:34Z" duration="0.000004" asserts="0">
|
||||
<properties>
|
||||
<property name="_JOINTYPE" value="UnityCombinatorial" />
|
||||
<property name="_SKIPREASON" value="ignore" />
|
||||
</properties>
|
||||
<reason>
|
||||
<message><![CDATA[ignore]]></message>
|
||||
</reason>
|
||||
</test-case>
|
||||
<test-case id="1014" name="PassedTest" fullname="Editor.EditorModeTest.PassedTest" methodname="PassedTest" classname="Editor.EditorModeTest" runstate="Runnable" seed="387558551" result="Passed" start-time="2021-01-19 20:51:34Z" end-time="2021-01-19 20:51:34Z" duration="0.000366" asserts="0">
|
||||
<properties />
|
||||
</test-case>
|
||||
<test-case id="1017" name="PassedUnityTest" fullname="Editor.EditorModeTest.PassedUnityTest" methodname="PassedUnityTest" classname="Editor.EditorModeTest" runstate="Runnable" seed="1069930397" result="Passed" start-time="2021-01-19 20:51:34Z" end-time="2021-01-19 20:51:34Z" duration="0.007903" asserts="0">
|
||||
<properties>
|
||||
<property name="_JOINTYPE" value="UnityCombinatorial" />
|
||||
</properties>
|
||||
</test-case>
|
||||
</test-suite>
|
||||
</test-suite>
|
||||
</test-suite>
|
||||
</test-suite>
|
||||
</test-run>
|
||||
111
artifacts/playmode-results.xml
Normal file
111
artifacts/playmode-results.xml
Normal file
@@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<test-run id="2" testcasecount="8" result="Failed(Child)" total="8" passed="2" failed="4" inconclusive="0" skipped="2" asserts="0" engine-version="3.5.0.0" clr-version="4.0.30319.42000" start-time="2021-01-19 20:51:44Z" end-time="2021-01-19 20:51:44Z" duration="0.1055695">
|
||||
<test-suite type="TestSuite" id="1000" name="sample-project" fullname="sample-project" runstate="Runnable" testcasecount="8" result="Failed" site="Child" start-time="2021-01-19 20:51:44Z" end-time="2021-01-19 20:51:44Z" duration="0.105570" total="8" passed="2" failed="4" inconclusive="0" skipped="2" asserts="0">
|
||||
<properties />
|
||||
<failure>
|
||||
<message><![CDATA[One or more child tests had errors]]></message>
|
||||
</failure>
|
||||
<test-suite type="Assembly" id="1012" name="Assembly-CSharp.dll" fullname="/github/workspace/unity-project/Library/ScriptAssemblies/Assembly-CSharp.dll" runstate="Runnable" testcasecount="8" result="Failed" site="Child" start-time="2021-01-19 20:51:44Z" end-time="2021-01-19 20:51:44Z" duration="0.088008" total="8" passed="2" failed="4" inconclusive="0" skipped="2" asserts="0">
|
||||
<properties>
|
||||
<property name="_PID" value="474" />
|
||||
<property name="_APPDOMAIN" value="Unity Child Domain" />
|
||||
<property name="platform" value="PlayMode" />
|
||||
</properties>
|
||||
<failure>
|
||||
<message><![CDATA[One or more child tests had errors]]></message>
|
||||
</failure>
|
||||
<test-suite type="TestSuite" id="1013" name="Tests" fullname="Tests" runstate="Runnable" testcasecount="8" result="Failed" site="Child" start-time="2021-01-19 20:51:44Z" end-time="2021-01-19 20:51:44Z" duration="0.084864" total="8" passed="2" failed="4" inconclusive="0" skipped="2" asserts="0">
|
||||
<properties />
|
||||
<failure>
|
||||
<message><![CDATA[One or more child tests had errors]]></message>
|
||||
</failure>
|
||||
<test-suite type="TestFixture" id="1001" name="PlayModeTest" fullname="Tests.PlayModeTest" classname="Tests.PlayModeTest" runstate="Runnable" testcasecount="6" result="Failed" site="Child" start-time="2021-01-19 20:51:44Z" end-time="2021-01-19 20:51:44Z" duration="0.074407" total="6" passed="2" failed="2" inconclusive="0" skipped="2" asserts="0">
|
||||
<properties />
|
||||
<failure>
|
||||
<message><![CDATA[One or more child tests had errors]]></message>
|
||||
</failure>
|
||||
<test-case id="1004" name="FailedTest" fullname="Tests.PlayModeTest.FailedTest" methodname="FailedTest" classname="Tests.PlayModeTest" runstate="Runnable" seed="1067965392" result="Failed" start-time="2021-01-19 20:51:44Z" end-time="2021-01-19 20:51:44Z" duration="0.034377" asserts="0">
|
||||
<properties />
|
||||
<failure>
|
||||
<message><![CDATA[ Expected: True
|
||||
But was: False
|
||||
]]></message>
|
||||
<stack-trace><![CDATA[at Tests.PlayModeTest.FailedTest () [0x00000] in /github/workspace/unity-project/Assets/Tests/PlayModeTest.cs:20
|
||||
]]></stack-trace>
|
||||
</failure>
|
||||
</test-case>
|
||||
<test-case id="1007" name="FailedUnityTest" fullname="Tests.PlayModeTest.FailedUnityTest" methodname="FailedUnityTest" classname="Tests.PlayModeTest" runstate="Runnable" seed="318345342" result="Failed" start-time="2021-01-19 20:51:44Z" end-time="2021-01-19 20:51:44Z" duration="0.009524" asserts="0">
|
||||
<properties>
|
||||
<property name="_JOINTYPE" value="UnityCombinatorial" />
|
||||
</properties>
|
||||
<failure>
|
||||
<message><![CDATA[ Expected: True
|
||||
But was: False
|
||||
]]></message>
|
||||
<stack-trace><![CDATA[at Tests.PlayModeTest+<FailedUnityTest>d__5.MoveNext () [0x0002e] in /github/workspace/unity-project/Assets/Tests/PlayModeTest.cs:39
|
||||
at UnityEngine.TestTools.TestEnumerator+<Execute>d__6.MoveNext () [0x00038] in /github/workspace/unity-project/Library/PackageCache/com.unity.test-framework@1.1.19/UnityEngine.TestRunner/NUnitExtensions/Attributes/TestEnumerator.cs:36
|
||||
]]></stack-trace>
|
||||
</failure>
|
||||
</test-case>
|
||||
<test-case id="1003" name="IgnoredTest" fullname="Tests.PlayModeTest.IgnoredTest" methodname="IgnoredTest" classname="Tests.PlayModeTest" runstate="Ignored" seed="1914466070" result="Skipped" label="Ignored" start-time="2021-01-19 20:51:44Z" end-time="2021-01-19 20:51:44Z" duration="0.000211" asserts="0">
|
||||
<properties>
|
||||
<property name="_SKIPREASON" value="ignore" />
|
||||
</properties>
|
||||
<reason>
|
||||
<message><![CDATA[ignore]]></message>
|
||||
</reason>
|
||||
</test-case>
|
||||
<test-case id="1006" name="IgnoredUnityTest" fullname="Tests.PlayModeTest.IgnoredUnityTest" methodname="IgnoredUnityTest" classname="Tests.PlayModeTest" runstate="Ignored" seed="475291067" result="Skipped" label="Ignored" start-time="2021-01-19 20:51:44Z" end-time="2021-01-19 20:51:44Z" duration="0.000004" asserts="0">
|
||||
<properties>
|
||||
<property name="_JOINTYPE" value="UnityCombinatorial" />
|
||||
<property name="_SKIPREASON" value="ignore" />
|
||||
</properties>
|
||||
<reason>
|
||||
<message><![CDATA[ignore]]></message>
|
||||
</reason>
|
||||
</test-case>
|
||||
<test-case id="1002" name="PassedTest" fullname="Tests.PlayModeTest.PassedTest" methodname="PassedTest" classname="Tests.PlayModeTest" runstate="Runnable" seed="635988114" result="Passed" start-time="2021-01-19 20:51:44Z" end-time="2021-01-19 20:51:44Z" duration="0.000400" asserts="0">
|
||||
<properties />
|
||||
</test-case>
|
||||
<test-case id="1005" name="PassedUnityTest" fullname="Tests.PlayModeTest.PassedUnityTest" methodname="PassedUnityTest" classname="Tests.PlayModeTest" runstate="Runnable" seed="881217608" result="Passed" start-time="2021-01-19 20:51:44Z" end-time="2021-01-19 20:51:44Z" duration="0.000736" asserts="0">
|
||||
<properties>
|
||||
<property name="_JOINTYPE" value="UnityCombinatorial" />
|
||||
</properties>
|
||||
</test-case>
|
||||
</test-suite>
|
||||
<test-suite type="TestFixture" id="1008" name="SetupFailedTest" fullname="Tests.SetupFailedTest" classname="Tests.SetupFailedTest" runstate="Runnable" testcasecount="1" result="Failed" site="Child" start-time="2021-01-19 20:51:44Z" end-time="2021-01-19 20:51:44Z" duration="0.004318" total="1" passed="0" failed="1" inconclusive="0" skipped="0" asserts="0">
|
||||
<properties />
|
||||
<failure>
|
||||
<message><![CDATA[One or more child tests had errors]]></message>
|
||||
</failure>
|
||||
<test-case id="1009" name="PassedTest" fullname="Tests.SetupFailedTest.PassedTest" methodname="PassedTest" classname="Tests.SetupFailedTest" runstate="Runnable" seed="1423699315" result="Failed" label="Error" start-time="2021-01-19 20:51:44Z" end-time="2021-01-19 20:51:44Z" duration="0.003206" asserts="0">
|
||||
<properties />
|
||||
<failure>
|
||||
<message><![CDATA[SetUp : System.NullReferenceException : Object reference not set to an instance of an object]]></message>
|
||||
<stack-trace><![CDATA[--SetUp
|
||||
at Tests.SetupFailedTest.SetUp () [0x00000] in /github/workspace/unity-project/Assets/Tests/SetupFailedTest.cs:10
|
||||
at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke(System.Reflection.MonoMethod,object,object[],System.Exception&)
|
||||
at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00032] in <9577ac7a62ef43179789031239ba8798>:0 ]]></stack-trace>
|
||||
</failure>
|
||||
</test-case>
|
||||
</test-suite>
|
||||
<test-suite type="TestFixture" id="1010" name="TearDownFailedTest" fullname="Tests.TearDownFailedTest" classname="Tests.TearDownFailedTest" runstate="Runnable" testcasecount="1" result="Failed" site="Child" start-time="2021-01-19 20:51:44Z" end-time="2021-01-19 20:51:44Z" duration="0.001857" total="1" passed="0" failed="1" inconclusive="0" skipped="0" asserts="0">
|
||||
<properties />
|
||||
<failure>
|
||||
<message><![CDATA[One or more child tests had errors]]></message>
|
||||
</failure>
|
||||
<test-case id="1011" name="PassedTest" fullname="Tests.TearDownFailedTest.PassedTest" methodname="PassedTest" classname="Tests.TearDownFailedTest" runstate="Runnable" seed="1384928637" result="Failed" label="Error" start-time="2021-01-19 20:51:44Z" end-time="2021-01-19 20:51:44Z" duration="0.000755" asserts="0">
|
||||
<properties />
|
||||
<failure>
|
||||
<message><![CDATA[TearDown : System.NullReferenceException : Object reference not set to an instance of an object]]></message>
|
||||
<stack-trace><![CDATA[--TearDown
|
||||
at Tests.TearDownFailedTest.TearDown () [0x00000] in /github/workspace/unity-project/Assets/Tests/TearDownFailedTest.cs:10
|
||||
at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke(System.Reflection.MonoMethod,object,object[],System.Exception&)
|
||||
at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00032] in <9577ac7a62ef43179789031239ba8798>:0 ]]></stack-trace>
|
||||
</failure>
|
||||
</test-case>
|
||||
</test-suite>
|
||||
</test-suite>
|
||||
</test-suite>
|
||||
</test-suite>
|
||||
</test-run>
|
||||
17
babel.config.js
Normal file
17
babel.config.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const esModules = ['lodash-es'].join('|');
|
||||
|
||||
module.exports = {
|
||||
ignore: [`/node_modules/(?!${esModules})`],
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
targets: {
|
||||
node: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
'@babel/typescript',
|
||||
],
|
||||
plugins: ['@babel/plugin-proposal-class-properties', '@babel/proposal-object-rest-spread'],
|
||||
};
|
||||
8
jest.config.js
Normal file
8
jest.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const esModules = ['lodash-es'].join('|');
|
||||
|
||||
module.exports = {
|
||||
testEnvironment: 'node',
|
||||
moduleFileExtensions: ['ts', 'js', 'jsx', 'json', 'vue'],
|
||||
transform: { '^.+\\.(ts|js|jsx)?$': 'babel-jest' },
|
||||
transformIgnorePatterns: [`/node_modules/(?!${esModules})`],
|
||||
};
|
||||
1
media/Discord-Logo.svg
Normal file
1
media/Discord-Logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 272.1"><style>.st0{fill:#7289DA;}</style><path class="st0" d="M142.8 120.1c-5.7 0-10.2 4.9-10.2 11s4.6 11 10.2 11c5.7 0 10.2-4.9 10.2-11s-4.6-11-10.2-11zM106.3 120.1c-5.7 0-10.2 4.9-10.2 11s4.6 11 10.2 11c5.7 0 10.2-4.9 10.2-11 .1-6.1-4.5-11-10.2-11z"/><path class="st0" d="M191.4 36.9h-134c-11.3 0-20.5 9.2-20.5 20.5v134c0 11.3 9.2 20.5 20.5 20.5h113.4l-5.3-18.3 12.8 11.8 12.1 11.1 21.6 18.7V57.4c-.1-11.3-9.3-20.5-20.6-20.5zm-38.6 129.5s-3.6-4.3-6.6-8c13.1-3.7 18.1-11.8 18.1-11.8-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.4-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.6-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.2-1.8-1-2.8-1.7-2.8-1.7s4.8 7.9 17.5 11.7c-3 3.8-6.7 8.2-6.7 8.2-22.1-.7-30.5-15.1-30.5-15.1 0-31.9 14.4-57.8 14.4-57.8 14.4-10.7 28-10.4 28-10.4l1 1.2c-18 5.1-26.2 13-26.2 13s2.2-1.2 5.9-2.8c10.7-4.7 19.2-5.9 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.5 0 0-7.9-7.5-24.9-12.6l1.4-1.6s13.7-.3 28 10.4c0 0 14.4 25.9 14.4 57.8 0-.1-8.4 14.3-30.5 15zM303.8 79.7h-33.2V117l22.1 19.9v-36.2h11.8c7.5 0 11.2 3.6 11.2 9.4v27.7c0 5.8-3.5 9.7-11.2 9.7h-34v21.1h33.2c17.8.1 34.5-8.8 34.5-29.2v-29.8c.1-20.8-16.6-29.9-34.4-29.9zm174 59.7v-30.6c0-11 19.8-13.5 25.8-2.5l18.3-7.4c-7.2-15.8-20.3-20.4-31.2-20.4-17.8 0-35.4 10.3-35.4 30.3v30.6c0 20.2 17.6 30.3 35 30.3 11.2 0 24.6-5.5 32-19.9l-19.6-9c-4.8 12.3-24.9 9.3-24.9-1.4zM417.3 113c-6.9-1.5-11.5-4-11.8-8.3.4-10.3 16.3-10.7 25.6-.8l14.7-11.3c-9.2-11.2-19.6-14.2-30.3-14.2-16.3 0-32.1 9.2-32.1 26.6 0 16.9 13 26 27.3 28.2 7.3 1 15.4 3.9 15.2 8.9-.6 9.5-20.2 9-29.1-1.8l-14.2 13.3c8.3 10.7 19.6 16.1 30.2 16.1 16.3 0 34.4-9.4 35.1-26.6 1-21.7-14.8-27.2-30.6-30.1zm-67 55.5h22.4V79.7h-22.4v88.8zM728 79.7h-33.2V117l22.1 19.9v-36.2h11.8c7.5 0 11.2 3.6 11.2 9.4v27.7c0 5.8-3.5 9.7-11.2 9.7h-34v21.1H728c17.8.1 34.5-8.8 34.5-29.2v-29.8c0-20.8-16.7-29.9-34.5-29.9zm-162.9-1.2c-18.4 0-36.7 10-36.7 30.5v30.3c0 20.3 18.4 30.5 36.9 30.5 18.4 0 36.7-10.2 36.7-30.5V109c0-20.4-18.5-30.5-36.9-30.5zm14.4 60.8c0 6.4-7.2 9.7-14.3 9.7-7.2 0-14.4-3.1-14.4-9.7V109c0-6.5 7-10 14-10 7.3 0 14.7 3.1 14.7 10v30.3zM682.4 109c-.5-20.8-14.7-29.2-33-29.2h-35.5v88.8h22.7v-28.2h4l20.6 28.2h28L665 138.1c10.7-3.4 17.4-12.7 17.4-29.1zm-32.6 12h-13.2v-20.3h13.2c14.1 0 14.1 20.3 0 20.3z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
67
package.json
Normal file
67
package.json
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"name": "unity-test-runner",
|
||||
"version": "0.0.0",
|
||||
"description": "Run tests for any Unity project.",
|
||||
"main": "action/index.js",
|
||||
"repository": "git@github.com:webbertakken/unity-test-runner.git",
|
||||
"author": "Webber <webber@takken.io>",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prebuild": "yarn",
|
||||
"build": "ncc build src --out action --minify",
|
||||
"lint": "prettier --check \"src/**/*.js\" && eslint src",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.2.6",
|
||||
"@actions/exec": "1.0.4",
|
||||
"@actions/github": "^4.0.0",
|
||||
"@octokit/types": "6.10.1",
|
||||
"handlebars": "4.7.7",
|
||||
"xml-js": "1.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.12.10",
|
||||
"@babel/core": "7.12.10",
|
||||
"@babel/plugin-proposal-class-properties": "^7.12.13",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.12.13",
|
||||
"@babel/preset-env": "7.12.11",
|
||||
"@babel/preset-typescript": "^7.12.17",
|
||||
"@zeit/ncc": "0.22.3",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-eslint": "10.1.0",
|
||||
"babel-jest": "^26.6.3",
|
||||
"eslint": "7.17.0",
|
||||
"eslint-config-airbnb": "18.2.1",
|
||||
"eslint-config-prettier": "7.1.0",
|
||||
"eslint-plugin-flowtype": "5.2.0",
|
||||
"eslint-plugin-import": "2.22.1",
|
||||
"eslint-plugin-jsx-a11y": "6.4.1",
|
||||
"eslint-plugin-prettier": "3.3.1",
|
||||
"eslint-plugin-react": "7.22.0",
|
||||
"eslint-plugin-unicorn": "25.0.1",
|
||||
"husky": "4.3.7",
|
||||
"jest": "26.6.3",
|
||||
"lint-staged": "10.5.3",
|
||||
"lodash-es": "4.17.20",
|
||||
"prettier": "2.2.1",
|
||||
"typescript": "^4.1.5"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged && yarn build && git add action/index.js"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx}": [
|
||||
"prettier --write",
|
||||
"eslint",
|
||||
"git add",
|
||||
"jest --findRelatedTests"
|
||||
],
|
||||
"*.{json,md,yaml,yml}": [
|
||||
"prettier --write",
|
||||
"git add"
|
||||
]
|
||||
}
|
||||
}
|
||||
51
src/index.js
Normal file
51
src/index.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import * as core from '@actions/core';
|
||||
import { Action, Docker, Input, ImageTag, Output, ResultsCheck } from './model';
|
||||
|
||||
async function action() {
|
||||
Action.checkCompatibility();
|
||||
|
||||
const { dockerfile, workspace, actionFolder } = Action;
|
||||
const {
|
||||
unityVersion,
|
||||
customImage,
|
||||
projectPath,
|
||||
testMode,
|
||||
artifactsPath,
|
||||
useHostNetwork,
|
||||
customParameters,
|
||||
githubToken,
|
||||
checkName,
|
||||
} = Input.getFromUser();
|
||||
const baseImage = ImageTag.createForBase({ version: unityVersion, customImage });
|
||||
|
||||
try {
|
||||
// Build docker image
|
||||
const actionImage = await Docker.build({ path: actionFolder, dockerfile, baseImage });
|
||||
|
||||
// Run docker image
|
||||
await Docker.run(actionImage, {
|
||||
workspace,
|
||||
unityVersion,
|
||||
projectPath,
|
||||
testMode,
|
||||
artifactsPath,
|
||||
useHostNetwork,
|
||||
customParameters,
|
||||
githubToken,
|
||||
});
|
||||
} finally {
|
||||
// Set output
|
||||
await Output.setArtifactsPath(artifactsPath);
|
||||
}
|
||||
|
||||
if (githubToken) {
|
||||
const failedTestCount = await ResultsCheck.createCheck(artifactsPath, githubToken, checkName);
|
||||
if (failedTestCount >= 1) {
|
||||
core.setFailed(`Test(s) Failed! Check '${checkName}' for details.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
action().catch(error => {
|
||||
core.setFailed(error.message);
|
||||
});
|
||||
48
src/model/action.js
Normal file
48
src/model/action.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import path from 'path';
|
||||
|
||||
class Action {
|
||||
static get supportedPlatforms() {
|
||||
return ['linux'];
|
||||
}
|
||||
|
||||
static get isRunningLocally() {
|
||||
return process.env.RUNNER_WORKSPACE === undefined;
|
||||
}
|
||||
|
||||
static get isRunningFromSource() {
|
||||
return path.basename(__dirname) === 'model';
|
||||
}
|
||||
|
||||
static get name() {
|
||||
return 'unity-test-runner';
|
||||
}
|
||||
|
||||
static get rootFolder() {
|
||||
if (Action.isRunningFromSource) {
|
||||
return path.dirname(path.dirname(path.dirname(__filename)));
|
||||
}
|
||||
|
||||
return path.dirname(path.dirname(__filename));
|
||||
}
|
||||
|
||||
static get actionFolder() {
|
||||
return `${Action.rootFolder}/action`;
|
||||
}
|
||||
|
||||
static get dockerfile() {
|
||||
return `${Action.actionFolder}/Dockerfile`;
|
||||
}
|
||||
|
||||
static get workspace() {
|
||||
return process.env.GITHUB_WORKSPACE;
|
||||
}
|
||||
|
||||
static checkCompatibility() {
|
||||
const currentPlatform = process.platform;
|
||||
if (!Action.supportedPlatforms.includes(currentPlatform)) {
|
||||
throw new Error(`Currently ${currentPlatform}-platform is not supported`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Action;
|
||||
36
src/model/action.test.js
Normal file
36
src/model/action.test.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import Action from './action';
|
||||
|
||||
describe('Action', () => {
|
||||
describe('compatibility check', () => {
|
||||
it('throws for anything other than linux', () => {
|
||||
if (process.platform !== 'linux') {
|
||||
expect(() => Action.checkCompatibility()).toThrow();
|
||||
} else {
|
||||
expect(() => Action.checkCompatibility()).not.toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the root folder of the action', () => {
|
||||
const { rootFolder, name } = Action;
|
||||
|
||||
expect(path.basename(rootFolder)).toStrictEqual(name);
|
||||
expect(fs.existsSync(rootFolder)).toStrictEqual(true);
|
||||
});
|
||||
|
||||
it('returns the action folder', () => {
|
||||
const { actionFolder } = Action;
|
||||
|
||||
expect(path.basename(actionFolder)).toStrictEqual('action');
|
||||
expect(fs.existsSync(actionFolder)).toStrictEqual(true);
|
||||
});
|
||||
|
||||
it('returns the docker file', () => {
|
||||
const { dockerfile } = Action;
|
||||
|
||||
expect(path.basename(dockerfile)).toStrictEqual('Dockerfile');
|
||||
expect(fs.existsSync(dockerfile)).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
73
src/model/docker.js
Normal file
73
src/model/docker.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import { exec } from '@actions/exec';
|
||||
import ImageTag from './image-tag';
|
||||
|
||||
class Docker {
|
||||
static async build(buildParameters, silent = false) {
|
||||
const { path, dockerfile, baseImage } = buildParameters;
|
||||
const { version } = baseImage;
|
||||
|
||||
const tag = ImageTag.createForAction(version);
|
||||
const command = `docker build ${path} \
|
||||
--file ${dockerfile} \
|
||||
--build-arg IMAGE=${baseImage} \
|
||||
--tag ${tag}`;
|
||||
|
||||
await exec(command, undefined, { silent });
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
static async run(image, parameters, silent = false) {
|
||||
const {
|
||||
unityVersion,
|
||||
workspace,
|
||||
projectPath,
|
||||
testMode,
|
||||
artifactsPath,
|
||||
useHostNetwork,
|
||||
customParameters,
|
||||
githubToken,
|
||||
} = parameters;
|
||||
|
||||
const command = `docker run \
|
||||
--workdir /github/workspace \
|
||||
--rm \
|
||||
--env UNITY_LICENSE \
|
||||
--env UNITY_LICENSE_FILE \
|
||||
--env UNITY_EMAIL \
|
||||
--env UNITY_PASSWORD \
|
||||
--env UNITY_SERIAL \
|
||||
--env UNITY_VERSION="${unityVersion}" \
|
||||
--env PROJECT_PATH="${projectPath}" \
|
||||
--env TEST_MODE="${testMode}" \
|
||||
--env ARTIFACTS_PATH="${artifactsPath}" \
|
||||
--env CUSTOM_PARAMETERS="${customParameters}" \
|
||||
--env HOME=/github/home \
|
||||
--env GITHUB_REF \
|
||||
--env GITHUB_SHA \
|
||||
--env GITHUB_REPOSITORY \
|
||||
--env GITHUB_ACTOR \
|
||||
--env GITHUB_WORKFLOW \
|
||||
--env GITHUB_HEAD_REF \
|
||||
--env GITHUB_BASE_REF \
|
||||
--env GITHUB_EVENT_NAME \
|
||||
--env GITHUB_WORKSPACE=/github/workspace \
|
||||
--env GITHUB_ACTION \
|
||||
--env GITHUB_EVENT_PATH \
|
||||
--env RUNNER_OS \
|
||||
--env RUNNER_TOOL_CACHE \
|
||||
--env RUNNER_TEMP \
|
||||
--env RUNNER_WORKSPACE \
|
||||
--volume "/var/run/docker.sock":"/var/run/docker.sock" \
|
||||
--volume "/home/runner/work/_temp/_github_home":"/github/home" \
|
||||
--volume "/home/runner/work/_temp/_github_workflow":"/github/workflow" \
|
||||
--volume "${workspace}":"/github/workspace" \
|
||||
${useHostNetwork ? '--net=host' : ''} \
|
||||
${githubToken ? '--env USE_EXIT_CODE=false' : '--env USE_EXIT_CODE=true'} \
|
||||
${image}`;
|
||||
|
||||
await exec(command, undefined, { silent });
|
||||
}
|
||||
}
|
||||
|
||||
export default Docker;
|
||||
25
src/model/docker.test.js
Normal file
25
src/model/docker.test.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import Action from './action';
|
||||
import Docker from './docker';
|
||||
import ImageTag from './image-tag';
|
||||
|
||||
describe('Docker', () => {
|
||||
it('builds', async () => {
|
||||
const path = Action.actionFolder;
|
||||
const dockerfile = `${path}/Dockerfile`;
|
||||
const image = new ImageTag({
|
||||
repository: '',
|
||||
name: 'alpine',
|
||||
version: '3',
|
||||
});
|
||||
|
||||
const baseImage = {
|
||||
toString: () => image.toString().slice(0, image.toString().lastIndexOf('-base-0')),
|
||||
version: image.version,
|
||||
};
|
||||
|
||||
const tag = await Docker.build({ path, dockerfile, baseImage }, true);
|
||||
|
||||
expect(tag).toBeInstanceOf(ImageTag);
|
||||
expect(tag.toString()).toStrictEqual('unity-action:3-base-0');
|
||||
}, 240000);
|
||||
});
|
||||
45
src/model/image-tag.js
Normal file
45
src/model/image-tag.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { trimStart } from 'lodash-es';
|
||||
|
||||
class ImageTag {
|
||||
static createForBase({ version, customImage }) {
|
||||
const repository = 'unityci';
|
||||
const name = 'editor';
|
||||
return new this({ repository, name, version, customImage });
|
||||
}
|
||||
|
||||
static createForAction(version) {
|
||||
const repository = '';
|
||||
const name = 'unity-action';
|
||||
return new this({ repository, name, version });
|
||||
}
|
||||
|
||||
constructor({ repository = '', name, version, customImage }) {
|
||||
if (!ImageTag.versionPattern.test(version)) {
|
||||
throw new Error(`Invalid version "${version}".`);
|
||||
}
|
||||
|
||||
Object.assign(this, { repository, name, version, customImage });
|
||||
}
|
||||
|
||||
static get versionPattern() {
|
||||
return /^20\d{2}\.\d\.\w{3,4}|3$/;
|
||||
}
|
||||
|
||||
get tag() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
get image() {
|
||||
return trimStart(`${this.repository}/${this.name}`, '/');
|
||||
}
|
||||
|
||||
toString() {
|
||||
if (this.customImage && this.customImage !== '') {
|
||||
return this.customImage;
|
||||
}
|
||||
|
||||
return `${this.image}:${this.tag}-base-0`;
|
||||
}
|
||||
}
|
||||
|
||||
export default ImageTag;
|
||||
47
src/model/image-tag.test.js
Normal file
47
src/model/image-tag.test.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import ImageTag from './image-tag';
|
||||
|
||||
describe('ImageTag', () => {
|
||||
describe('constructor', () => {
|
||||
const some = {
|
||||
name: 'someName',
|
||||
version: '2020.0.00f0',
|
||||
};
|
||||
|
||||
it('can be called', () => {
|
||||
expect(() => new ImageTag(some)).not.toThrow();
|
||||
});
|
||||
|
||||
it('accepts parameters and sets the right properties', () => {
|
||||
const image = new ImageTag(some);
|
||||
|
||||
expect(image.repository).toStrictEqual('');
|
||||
expect(image.name).toStrictEqual(some.name);
|
||||
expect(image.version).toStrictEqual(some.version);
|
||||
});
|
||||
|
||||
test.each(['2000.0.0f0', '2011.1.11f1'])('accepts %p version format', version => {
|
||||
expect(() => new ImageTag({ version })).not.toThrow();
|
||||
});
|
||||
|
||||
test.each(['some version', '', 1, undefined])('throws for incorrect versions %p', version => {
|
||||
expect(() => new ImageTag({ version })).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('toString', () => {
|
||||
it('returns the correct version', () => {
|
||||
const image = ImageTag.createForBase({ version: '2099.1.1111' });
|
||||
|
||||
expect(image.toString()).toStrictEqual(`unityci/editor:2099.1.1111-base-0`);
|
||||
});
|
||||
|
||||
it('returns customImage if given', () => {
|
||||
const image = ImageTag.createForBase({
|
||||
version: '2099.1.1111',
|
||||
customImage: 'unityci/editor:2099.1.1111-base-0',
|
||||
});
|
||||
|
||||
expect(image.toString()).toStrictEqual(image.customImage);
|
||||
});
|
||||
});
|
||||
});
|
||||
8
src/model/index.js
Normal file
8
src/model/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import Action from './action';
|
||||
import Docker from './docker';
|
||||
import Input from './input';
|
||||
import ImageTag from './image-tag';
|
||||
import Output from './output';
|
||||
import ResultsCheck from './results-check';
|
||||
|
||||
export { Action, Docker, Input, ImageTag, Output, ResultsCheck };
|
||||
10
src/model/index.test.js
Normal file
10
src/model/index.test.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import * as Index from '.';
|
||||
|
||||
describe('Index', () => {
|
||||
test.each(['Action', 'Docker', 'ImageTag', 'Input', 'Output', 'ResultsCheck'])(
|
||||
'exports %s',
|
||||
exportedModule => {
|
||||
expect(typeof Index[exportedModule]).toStrictEqual('function');
|
||||
},
|
||||
);
|
||||
});
|
||||
67
src/model/input.js
Normal file
67
src/model/input.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import { getInput } from '@actions/core';
|
||||
import { includes } from 'lodash-es';
|
||||
import UnityVersionParser from './unity-version-parser';
|
||||
|
||||
class Input {
|
||||
static get testModes() {
|
||||
return ['all', 'playmode', 'editmode'];
|
||||
}
|
||||
|
||||
static isValidFolderName(folderName) {
|
||||
const validFolderName = new RegExp(/^(\.|\.\/)?(\.?\w+([_-]?\w+)*\/?)*$/);
|
||||
|
||||
return validFolderName.test(folderName);
|
||||
}
|
||||
|
||||
static getFromUser() {
|
||||
// Input variables specified in workflow using "with" prop.
|
||||
const rawUnityVersion = getInput('unityVersion') || 'auto';
|
||||
const customImage = getInput('customImage') || '';
|
||||
const testMode = getInput('testMode') || 'all';
|
||||
const rawProjectPath = getInput('projectPath') || '.';
|
||||
const rawArtifactsPath = getInput('artifactsPath') || 'artifacts';
|
||||
const rawUseHostNetwork = getInput('useHostNetwork') || 'false';
|
||||
const customParameters = getInput('customParameters') || '';
|
||||
const githubToken = getInput('githubToken') || '';
|
||||
const checkName = getInput('checkName') || 'Test Results';
|
||||
|
||||
// Validate input
|
||||
if (!includes(this.testModes, testMode)) {
|
||||
throw new Error(`Invalid testMode ${testMode}`);
|
||||
}
|
||||
|
||||
if (!this.isValidFolderName(rawArtifactsPath)) {
|
||||
throw new Error(`Invalid artifactsPath "${rawArtifactsPath}"`);
|
||||
}
|
||||
|
||||
if (!this.isValidFolderName(rawProjectPath)) {
|
||||
throw new Error(`Invalid projectPath "${rawProjectPath}"`);
|
||||
}
|
||||
|
||||
if (rawUseHostNetwork !== 'true' && rawUseHostNetwork !== 'false') {
|
||||
throw new Error(`Invalid useHostNetwork "${rawUseHostNetwork}"`);
|
||||
}
|
||||
|
||||
// Sanitise input
|
||||
const projectPath = rawProjectPath.replace(/\/$/, '');
|
||||
const artifactsPath = rawArtifactsPath.replace(/\/$/, '');
|
||||
const useHostNetwork = rawUseHostNetwork === 'true';
|
||||
const unityVersion =
|
||||
rawUnityVersion === 'auto' ? UnityVersionParser.read(projectPath) : rawUnityVersion;
|
||||
|
||||
// Return sanitised input
|
||||
return {
|
||||
unityVersion,
|
||||
customImage,
|
||||
projectPath,
|
||||
testMode,
|
||||
artifactsPath,
|
||||
useHostNetwork,
|
||||
customParameters,
|
||||
githubToken,
|
||||
checkName,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default Input;
|
||||
34
src/model/input.test.js
Normal file
34
src/model/input.test.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import Input from './input';
|
||||
|
||||
describe('Input', () => {
|
||||
describe('getFromUser', () => {
|
||||
it('does not throw', () => {
|
||||
expect(() => Input.getFromUser()).not.toThrow();
|
||||
});
|
||||
|
||||
it('returns an object', () => {
|
||||
expect(typeof Input.getFromUser()).toStrictEqual('object');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidFolderName', () => {
|
||||
test.each([
|
||||
'.',
|
||||
'./',
|
||||
'folder',
|
||||
'trailing/',
|
||||
'.hidden',
|
||||
'.hidden/sub',
|
||||
'.hidden/trailing/',
|
||||
'./.hidden-sub',
|
||||
'hyphen-folder',
|
||||
'under_score',
|
||||
])('returns true for %s', folderName => {
|
||||
expect(Input.isValidFolderName(folderName)).toStrictEqual(true);
|
||||
});
|
||||
|
||||
test.each(['..', '../'])('returns false for %s', folderName => {
|
||||
expect(Input.isValidFolderName(folderName)).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
9
src/model/output.js
Normal file
9
src/model/output.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const core = require('@actions/core');
|
||||
|
||||
class Output {
|
||||
static async setArtifactsPath(artifactsPath) {
|
||||
await core.setOutput('artifactsPath', artifactsPath);
|
||||
}
|
||||
}
|
||||
|
||||
export default Output;
|
||||
9
src/model/output.test.js
Normal file
9
src/model/output.test.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import Output from './output';
|
||||
|
||||
describe('Output', () => {
|
||||
describe('setArtifactsPath', () => {
|
||||
it('does not throw', async () => {
|
||||
await expect(Output.setArtifactsPath()).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
120
src/model/results-check.js
Normal file
120
src/model/results-check.js
Normal file
@@ -0,0 +1,120 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as github from '@actions/github';
|
||||
import * as fs from 'fs';
|
||||
import path from 'path';
|
||||
import Handlebars from 'handlebars';
|
||||
import ResultsParser from './results-parser';
|
||||
import { RunMeta } from './ts/results-meta.ts';
|
||||
|
||||
class ResultsCheck {
|
||||
static async createCheck(artifactsPath, githubToken, checkName) {
|
||||
// Validate input
|
||||
if (!fs.existsSync(artifactsPath) || !githubToken || !checkName) {
|
||||
throw new Error(
|
||||
`Missing input! {"artifactsPath": "${artifactsPath}", "githubToken": "${githubToken}, "checkName": "${checkName}"`,
|
||||
);
|
||||
}
|
||||
|
||||
// Parse all results files
|
||||
const runs = [];
|
||||
const files = fs.readdirSync(artifactsPath);
|
||||
await Promise.all(
|
||||
files.map(async filepath => {
|
||||
if (!filepath.endsWith('.xml')) return;
|
||||
core.info(`Processing file ${filepath}...`);
|
||||
const fileData = await ResultsParser.parseResults(path.join(artifactsPath, filepath));
|
||||
core.info(fileData.summary);
|
||||
runs.push(fileData);
|
||||
}),
|
||||
);
|
||||
|
||||
// Combine all results into a single run summary
|
||||
const runSummary = new RunMeta(checkName);
|
||||
runs.forEach(run => {
|
||||
runSummary.total += run.total;
|
||||
runSummary.passed += run.passed;
|
||||
runSummary.skipped += run.skipped;
|
||||
runSummary.failed += run.failed;
|
||||
runSummary.duration += run.duration;
|
||||
run.suites.forEach(suite => {
|
||||
runSummary.addTests(suite.tests);
|
||||
});
|
||||
});
|
||||
|
||||
// Log
|
||||
core.info('=================');
|
||||
core.info('Analyze result:');
|
||||
core.info(runSummary.summary);
|
||||
|
||||
// Format output
|
||||
const title = runSummary.summary;
|
||||
const summary = await ResultsCheck.renderSummary(runs);
|
||||
core.debug(`Summary view: ${summary}`);
|
||||
const details = await ResultsCheck.renderDetails(runs);
|
||||
core.debug(`Details view: ${details}`);
|
||||
const rawAnnotations = runSummary.extractAnnotations();
|
||||
core.debug(`Raw annotations: ${rawAnnotations}`);
|
||||
const annotations = rawAnnotations.map(rawAnnotation => {
|
||||
const annotation = rawAnnotation;
|
||||
annotation.path = rawAnnotation.path.replace('/github/workspace/', '');
|
||||
return annotation;
|
||||
});
|
||||
core.debug(`Annotations: ${annotations}`);
|
||||
const output = {
|
||||
title,
|
||||
summary,
|
||||
text: details,
|
||||
annotations: annotations.slice(0, 50),
|
||||
};
|
||||
|
||||
// Call GitHub API
|
||||
await ResultsCheck.requestGitHubCheck(githubToken, checkName, output);
|
||||
return runSummary.failed;
|
||||
}
|
||||
|
||||
static async requestGitHubCheck(githubToken, checkName, output) {
|
||||
const pullRequest = github.context.payload.pull_request;
|
||||
const headSha = (pullRequest && pullRequest.head.sha) || github.context.sha;
|
||||
|
||||
core.info(`Posting results for ${headSha}`);
|
||||
const createCheckRequest = {
|
||||
...github.context.repo,
|
||||
name: checkName,
|
||||
head_sha: headSha,
|
||||
status: 'completed',
|
||||
conclusion: 'neutral',
|
||||
output,
|
||||
};
|
||||
|
||||
const octokit = github.getOctokit(githubToken);
|
||||
await octokit.checks.create(createCheckRequest);
|
||||
}
|
||||
|
||||
static async renderSummary(runMetas) {
|
||||
return ResultsCheck.render(`${__dirname}/../views/results-check-summary.hbs`, runMetas);
|
||||
}
|
||||
|
||||
static async renderDetails(runMetas) {
|
||||
return ResultsCheck.render(`${__dirname}/../views/results-check-details.hbs`, runMetas);
|
||||
}
|
||||
|
||||
static async render(viewPath, runMetas) {
|
||||
Handlebars.registerHelper('indent', toIndent =>
|
||||
toIndent
|
||||
.split('\n')
|
||||
.map(s => ` ${s.replace('/github/workspace/', '')}`)
|
||||
.join('\n'),
|
||||
);
|
||||
const source = await fs.promises.readFile(viewPath, 'utf8');
|
||||
const template = Handlebars.compile(source);
|
||||
return template(
|
||||
{ runs: runMetas },
|
||||
{
|
||||
allowProtoMethodsByDefault: true,
|
||||
allowProtoPropertiesByDefault: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ResultsCheck;
|
||||
9
src/model/results-check.test.js
Normal file
9
src/model/results-check.test.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import ResultsCheck from './results-check';
|
||||
|
||||
describe('ResultsCheck', () => {
|
||||
describe('createCheck', () => {
|
||||
it('throws for missing input', () => {
|
||||
expect(() => ResultsCheck.createCheck('', '', '')).rejects.toEqual(Error);
|
||||
});
|
||||
});
|
||||
});
|
||||
123
src/model/results-parser.js
Normal file
123
src/model/results-parser.js
Normal file
@@ -0,0 +1,123 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as xmljs from 'xml-js';
|
||||
import * as fs from 'fs';
|
||||
import path from 'path';
|
||||
import { RunMeta, TestMeta } from './ts/results-meta.ts';
|
||||
|
||||
class ResultsParser {
|
||||
static async parseResults(filepath) {
|
||||
if (!fs.existsSync(filepath)) {
|
||||
throw new Error(`Missing file! {"filepath": "${filepath}"}`);
|
||||
}
|
||||
|
||||
core.info(`Trying to open ${filepath}`);
|
||||
const file = await fs.promises.readFile(filepath, 'utf8');
|
||||
const results = xmljs.xml2js(file, { compact: true });
|
||||
core.info(`File ${filepath} parsed...`);
|
||||
|
||||
return ResultsParser.convertResults(path.basename(filepath), results);
|
||||
}
|
||||
|
||||
static convertResults(filename, filedata) {
|
||||
core.info(`Start analyzing results: ${filename}`);
|
||||
|
||||
const run = filedata['test-run'];
|
||||
const runMeta = new RunMeta(filename);
|
||||
const tests = ResultsParser.convertSuite(run['test-suite']);
|
||||
core.debug(tests);
|
||||
|
||||
runMeta.total = Number(run._attributes.total);
|
||||
runMeta.failed = Number(run._attributes.failed);
|
||||
runMeta.skipped = Number(run._attributes.skipped);
|
||||
runMeta.passed = Number(run._attributes.passed);
|
||||
runMeta.duration = Number(run._attributes.duration);
|
||||
runMeta.addTests(tests);
|
||||
|
||||
return runMeta;
|
||||
}
|
||||
|
||||
static convertSuite(suites) {
|
||||
if (Array.isArray(suites)) {
|
||||
const innerResult = [];
|
||||
suites.forEach(suite => {
|
||||
innerResult.push(...ResultsParser.convertSuite(suite));
|
||||
});
|
||||
return innerResult;
|
||||
}
|
||||
|
||||
const result = [];
|
||||
const innerSuite = suites['test-suite'];
|
||||
if (innerSuite) {
|
||||
result.push(...ResultsParser.convertSuite(innerSuite));
|
||||
}
|
||||
|
||||
const tests = suites['test-case'];
|
||||
if (tests) {
|
||||
result.push(...ResultsParser.convertTests(suites._attributes.fullname, tests));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static convertTests(suite, tests) {
|
||||
if (Array.isArray(tests)) {
|
||||
const result = [];
|
||||
tests.forEach(test => {
|
||||
result.push(ResultsParser.convertTestCase(suite, test));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
return [ResultsParser.convertTestCase(suite, tests)];
|
||||
}
|
||||
|
||||
static convertTestCase(suite, testCase) {
|
||||
const { _attributes, failure } = testCase;
|
||||
const { name, fullname, result, duration } = _attributes;
|
||||
const testMeta = new TestMeta(suite, name);
|
||||
testMeta.result = result;
|
||||
testMeta.duration = Number(duration);
|
||||
|
||||
if (!failure) {
|
||||
core.debug(`Skip test ${fullname} without failure data`);
|
||||
return testMeta;
|
||||
}
|
||||
|
||||
core.debug(`Convert data for test ${fullname}`);
|
||||
if (failure['stack-trace'] === undefined) {
|
||||
core.warning(`No stack trace for test case: ${fullname}`);
|
||||
return testMeta;
|
||||
}
|
||||
|
||||
const trace = failure['stack-trace']._cdata;
|
||||
const point = ResultsParser.findAnnotationPoint(trace);
|
||||
if (!point.path || !point.line) {
|
||||
core.warning(`Not able to find annotation point for failed test! Test trace: ${trace}`);
|
||||
return testMeta;
|
||||
}
|
||||
|
||||
testMeta.annotation = {
|
||||
path: point.path,
|
||||
start_line: point.line,
|
||||
end_line: point.line,
|
||||
annotation_level: 'failure',
|
||||
title: fullname,
|
||||
message: failure.message._cdata,
|
||||
raw_details: trace,
|
||||
};
|
||||
core.info(
|
||||
`- ${testMeta.annotation.path}:${testMeta.annotation.start_line} - ${testMeta.annotation.title}`,
|
||||
);
|
||||
return testMeta;
|
||||
}
|
||||
|
||||
static findAnnotationPoint(trace) {
|
||||
const match = trace.match(/at .* in ((?<path>[^:]+):(?<line>\d+))/);
|
||||
return {
|
||||
path: match ? match.groups.path : '',
|
||||
line: match ? Number(match.groups.line) : 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default ResultsParser;
|
||||
205
src/model/results-parser.test.js
Normal file
205
src/model/results-parser.test.js
Normal file
@@ -0,0 +1,205 @@
|
||||
import * as xmljs from 'xml-js';
|
||||
import * as fs from 'fs';
|
||||
import ResultsParser from './results-parser';
|
||||
|
||||
describe('ResultsParser', () => {
|
||||
describe('parseResults', () => {
|
||||
it('throws for missing file', () => {
|
||||
expect(() => ResultsParser.parseResults('')).rejects.toEqual(Error);
|
||||
});
|
||||
|
||||
it('parses editmode-results.xml', () => {
|
||||
expect(() => ResultsParser.parseResults('./artifacts/editmode-results.xml')).not.toThrow();
|
||||
});
|
||||
|
||||
it('parses playmode-results.xml', () => {
|
||||
expect(() => ResultsParser.parseResults('./artifacts/playmode-results.xml')).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertResults', () => {
|
||||
it('converts editmode-results.xml', () => {
|
||||
const file = fs.readFileSync('./artifacts/editmode-results.xml');
|
||||
const filedata = xmljs.xml2js(file, { compact: true });
|
||||
const result = ResultsParser.convertResults('editmode-results.xml', filedata);
|
||||
expect(result.suites.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('converts playmode-results.xml', () => {
|
||||
const file = fs.readFileSync('./artifacts/playmode-results.xml');
|
||||
const filedata = xmljs.xml2js(file, { compact: true });
|
||||
const result = ResultsParser.convertResults('playmode-results.xml', filedata);
|
||||
expect(result.suites.length).toEqual(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertSuite', () => {
|
||||
test('convert single', () => {
|
||||
const targetSuite = {
|
||||
_attributes: {
|
||||
fullname: 'Suite Full Name',
|
||||
},
|
||||
'test-case': [{ _attributes: { name: 'testA' } }, { _attributes: { name: 'testB' } }],
|
||||
'test-suite': [
|
||||
{
|
||||
_attributes: {
|
||||
fullname: 'Inner Suite Full Name',
|
||||
},
|
||||
'test-case': { _attributes: { name: 'testC' } },
|
||||
'test-suite': [],
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = ResultsParser.convertSuite(targetSuite);
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
annotation: undefined,
|
||||
duration: Number.NaN,
|
||||
result: undefined,
|
||||
suite: 'Inner Suite Full Name',
|
||||
title: 'testC',
|
||||
},
|
||||
{
|
||||
annotation: undefined,
|
||||
duration: Number.NaN,
|
||||
result: undefined,
|
||||
suite: 'Suite Full Name',
|
||||
title: 'testA',
|
||||
},
|
||||
{
|
||||
annotation: undefined,
|
||||
duration: Number.NaN,
|
||||
result: undefined,
|
||||
suite: 'Suite Full Name',
|
||||
title: 'testB',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertTests', () => {
|
||||
test('convert array', () => {
|
||||
const testA = { _attributes: { name: 'testA' } };
|
||||
const testB = { _attributes: { name: 'testB' } };
|
||||
const testResult = [testA, testB];
|
||||
const result = ResultsParser.convertTests('Test Suite', testResult);
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{ suite: 'Test Suite', title: 'testA' },
|
||||
{ suite: 'Test Suite', title: 'testB' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('convert single', () => {
|
||||
const testA = { _attributes: { name: 'testA' } };
|
||||
const result = ResultsParser.convertTests('Test Suite', testA);
|
||||
|
||||
expect(result).toMatchObject([{ suite: 'Test Suite', title: 'testA' }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertTestCase', () => {
|
||||
test('not failed', () => {
|
||||
const result = ResultsParser.convertTestCase('Test Suite', {
|
||||
_attributes: {
|
||||
name: 'Test Name',
|
||||
duration: '3.14',
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.suite).toBe('Test Suite');
|
||||
expect(result.title).toBe('Test Name');
|
||||
expect(result.duration).toBe(3.14);
|
||||
expect(result.annotation).toBeUndefined();
|
||||
});
|
||||
|
||||
test('no stack trace', () => {
|
||||
const result = ResultsParser.convertTestCase('Test Suite', {
|
||||
_attributes: {
|
||||
name: 'Test Name',
|
||||
duration: '3.14',
|
||||
},
|
||||
failure: {
|
||||
message: { _cdata: 'Message CDATA' },
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.suite).toBe('Test Suite');
|
||||
expect(result.title).toBe('Test Name');
|
||||
expect(result.duration).toBe(3.14);
|
||||
expect(result.annotation).toBeUndefined();
|
||||
});
|
||||
|
||||
test('no annotation path', () => {
|
||||
const result = ResultsParser.convertTestCase('Test Suite', {
|
||||
_attributes: {
|
||||
name: 'Test Name',
|
||||
duration: '3.14',
|
||||
},
|
||||
failure: {
|
||||
message: { _cdata: 'Message CDATA' },
|
||||
'stack-trace': { _cdata: 'Test CDATA' },
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.suite).toBe('Test Suite');
|
||||
expect(result.title).toBe('Test Name');
|
||||
expect(result.duration).toBe(3.14);
|
||||
expect(result.annotation).toBeUndefined();
|
||||
});
|
||||
|
||||
test('prepare annotation', () => {
|
||||
const result = ResultsParser.convertTestCase('Test Suite', {
|
||||
_attributes: {
|
||||
name: 'Test Name',
|
||||
fullname: 'Test Full Name',
|
||||
duration: '3.14',
|
||||
},
|
||||
failure: {
|
||||
message: { _cdata: 'Message CDATA' },
|
||||
'stack-trace': {
|
||||
_cdata:
|
||||
'at Tests.SetupFailedTest.SetUp () [0x00000] in /github/workspace/unity-project/Assets/Tests/SetupFailedTest.cs:10',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.suite).toBe('Test Suite');
|
||||
expect(result.title).toBe('Test Name');
|
||||
expect(result.duration).toBe(3.14);
|
||||
expect(result.annotation).toMatchObject({
|
||||
annotation_level: 'failure',
|
||||
end_line: 10,
|
||||
message: 'Message CDATA',
|
||||
path: '/github/workspace/unity-project/Assets/Tests/SetupFailedTest.cs',
|
||||
raw_details:
|
||||
'at Tests.SetupFailedTest.SetUp () [0x00000] in /github/workspace/unity-project/Assets/Tests/SetupFailedTest.cs:10',
|
||||
start_line: 10,
|
||||
title: 'Test Full Name',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAnnotationPoint', () => {
|
||||
test('keep working if not matching', () => {
|
||||
const result = ResultsParser.findAnnotationPoint('');
|
||||
expect(result.path).toBe('');
|
||||
expect(result.line).toBe(0);
|
||||
});
|
||||
|
||||
test('simple annotation point', () => {
|
||||
const result = ResultsParser.findAnnotationPoint(`at Tests.PlayModeTest+<FailedUnityTest>d__5.MoveNext () [0x0002e] in /github/workspace/unity-project/Assets/Tests/PlayModeTest.cs:39
|
||||
at UnityEngine.TestTools.TestEnumerator+<Execute>d__6.MoveNext () [0x00038] in /github/workspace/unity-project/Library/PackageCache/com.unity.test-framework@1.1.19/UnityEngine.TestRunner/NUnitExtensions/Attributes/TestEnumerator.cs:36`);
|
||||
expect(result.path).toBe('/github/workspace/unity-project/Assets/Tests/PlayModeTest.cs');
|
||||
expect(result.line).toBe(39);
|
||||
});
|
||||
|
||||
test('setup annotation point', () => {
|
||||
const result = ResultsParser.findAnnotationPoint(`SetUp
|
||||
at Tests.SetupFailedTest.SetUp () [0x00000] in /github/workspace/unity-project/Assets/Tests/SetupFailedTest.cs:10`);
|
||||
expect(result.path).toBe('/github/workspace/unity-project/Assets/Tests/SetupFailedTest.cs');
|
||||
expect(result.line).toBe(10);
|
||||
});
|
||||
});
|
||||
});
|
||||
118
src/model/ts/results-meta.ts
Normal file
118
src/model/ts/results-meta.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { components } from '@octokit/openapi-types/dist-types/generated/types';
|
||||
|
||||
export function timeHelper(seconds: number): string {
|
||||
return `${seconds.toFixed(3)}s`;
|
||||
}
|
||||
|
||||
export abstract class Meta {
|
||||
title: string;
|
||||
duration = 0;
|
||||
|
||||
constructor(title: string) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
abstract get summary(): string;
|
||||
|
||||
abstract get mark(): string;
|
||||
}
|
||||
|
||||
export type Annotation = components['schemas']['check-annotation'];
|
||||
|
||||
export class RunMeta extends Meta {
|
||||
total = 0;
|
||||
passed = 0;
|
||||
skipped = 0;
|
||||
failed = 0;
|
||||
|
||||
tests: TestMeta[] = [];
|
||||
suites: RunMeta[] = [];
|
||||
|
||||
extractAnnotations(): Annotation[] {
|
||||
const result = [] as Annotation[];
|
||||
for (const suite of this.suites) {
|
||||
result.push(...suite.extractAnnotations());
|
||||
}
|
||||
for (const test of this.tests) {
|
||||
if (test.annotation !== undefined) {
|
||||
result.push(test.annotation);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
addTests(testSuite: TestMeta[]): void {
|
||||
testSuite.forEach(test => {
|
||||
this.addTest(test);
|
||||
});
|
||||
}
|
||||
|
||||
addTest(test: TestMeta): void {
|
||||
if (test.suite === undefined) {
|
||||
return;
|
||||
}
|
||||
if (test.suite === this.title) {
|
||||
this.total++;
|
||||
this.duration += test.duration;
|
||||
this.tests.push(test);
|
||||
if (test.result === 'Passed') this.passed++;
|
||||
else if (test.result === 'Failed') this.failed++;
|
||||
else this.skipped++;
|
||||
return;
|
||||
}
|
||||
|
||||
let target = this.suites.find(s => s.title === test.suite);
|
||||
if (target === undefined) {
|
||||
target = new RunMeta(test.suite);
|
||||
this.suites.push(target);
|
||||
}
|
||||
|
||||
target.addTest(test);
|
||||
}
|
||||
|
||||
get summary(): string {
|
||||
const result = this.failed > 0 ? 'Failed' : 'Passed';
|
||||
const sPart = this.skipped > 0 ? `, skipped: ${this.skipped}` : '';
|
||||
const fPart = this.failed > 0 ? `, failed: ${this.failed}` : '';
|
||||
const dPart = ` in ${timeHelper(this.duration)}`;
|
||||
return `${this.mark} ${this.title} - ${this.passed}/${this.total}${sPart}${fPart} - ${result}${dPart}`;
|
||||
}
|
||||
|
||||
get mark(): string {
|
||||
if (this.failed > 0) return '❌️';
|
||||
else if (this.skipped === 0) return '✅';
|
||||
return '⚠️';
|
||||
}
|
||||
}
|
||||
|
||||
export class TestMeta extends Meta {
|
||||
suite: string;
|
||||
result: string | undefined;
|
||||
annotation: Annotation | undefined;
|
||||
|
||||
constructor(suite: string, title: string) {
|
||||
super(title);
|
||||
this.suite = suite;
|
||||
}
|
||||
|
||||
isSkipped(): boolean {
|
||||
return this.result === 'Skipped';
|
||||
}
|
||||
|
||||
isFailed(): boolean {
|
||||
return this.result === 'Failed';
|
||||
}
|
||||
|
||||
get summary(): string {
|
||||
const dPart = this.isSkipped()
|
||||
? ''
|
||||
: ` in ${timeHelper(this.duration)}`;
|
||||
return `${this.mark} **${this.title}** - ${this.result}${dPart}`;
|
||||
}
|
||||
|
||||
get mark(): string {
|
||||
if (this.isFailed()) return '❌️';
|
||||
else if (this.isSkipped()) return '⚠️';
|
||||
return '✅';
|
||||
}
|
||||
}
|
||||
58
src/model/ts/results-report.ts
Normal file
58
src/model/ts/results-report.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
interface CommonAttributes {
|
||||
id: string;
|
||||
result: string;
|
||||
asserts: string;
|
||||
|
||||
'start-time': string;
|
||||
'end-time': string;
|
||||
duration: string;
|
||||
}
|
||||
|
||||
interface CommonSuiteAttributes extends CommonAttributes {
|
||||
total: string;
|
||||
passed: string;
|
||||
failed: string;
|
||||
skipped: string;
|
||||
}
|
||||
|
||||
export interface TestRun {
|
||||
_attributes: TestRunAttributes;
|
||||
'test-suite': TestSuite | TestSuite[];
|
||||
}
|
||||
|
||||
export interface TestRunAttributes extends CommonSuiteAttributes {
|
||||
testcasecount: string;
|
||||
'engine-version': string;
|
||||
}
|
||||
|
||||
export interface TestSuite {
|
||||
_attributes: TestSuiteAttributes;
|
||||
'test-suite': TestSuite | TestSuite[];
|
||||
'test-case': TestCase | TestCase[];
|
||||
failure?: FailureMessage;
|
||||
}
|
||||
|
||||
export interface TestSuiteAttributes extends CommonSuiteAttributes {
|
||||
type: string;
|
||||
name: string;
|
||||
fullname: string;
|
||||
}
|
||||
|
||||
export interface TestCase {
|
||||
_attributes: TestCaseAttributes;
|
||||
failure?: FailureMessage;
|
||||
}
|
||||
|
||||
export interface TestCaseAttributes extends CommonAttributes {
|
||||
name: string;
|
||||
fullname: string;
|
||||
methodname: string;
|
||||
classname: string;
|
||||
runstate: string;
|
||||
seed: string;
|
||||
}
|
||||
|
||||
export interface FailureMessage {
|
||||
message: { _cdata: string };
|
||||
'stack-trace'?: { _cdata: string };
|
||||
}
|
||||
26
src/model/unity-version-parser.js
Normal file
26
src/model/unity-version-parser.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
class UnityVersionParser {
|
||||
static get versionPattern() {
|
||||
return /20\d{2}\.\d\.\w{3,4}|3/;
|
||||
}
|
||||
|
||||
static parse(projectVersionTxt) {
|
||||
const matches = projectVersionTxt.match(UnityVersionParser.versionPattern);
|
||||
if (!matches || matches.length === 0) {
|
||||
throw new Error(`Failed to parse version from "${projectVersionTxt}".`);
|
||||
}
|
||||
return matches[0];
|
||||
}
|
||||
|
||||
static read(projectPath) {
|
||||
const filePath = path.join(projectPath, 'ProjectSettings', 'ProjectVersion.txt');
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return 'auto';
|
||||
}
|
||||
return UnityVersionParser.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
}
|
||||
}
|
||||
|
||||
export default UnityVersionParser;
|
||||
25
src/model/unity-version-parser.test.js
Normal file
25
src/model/unity-version-parser.test.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import UnityVersionParser from './unity-version-parser';
|
||||
|
||||
describe('UnityVersionParser', () => {
|
||||
describe('parse', () => {
|
||||
it('throws for empty string', () => {
|
||||
expect(() => UnityVersionParser.parse('')).toThrow(Error);
|
||||
});
|
||||
|
||||
it('parses from ProjectVersion.txt', () => {
|
||||
const projectVersionContents = `m_EditorVersion: 2019.2.11f1
|
||||
m_EditorVersionWithRevision: 2019.2.11f1 (5f859a4cfee5)`;
|
||||
expect(UnityVersionParser.parse(projectVersionContents)).toBe('2019.2.11f1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('read', () => {
|
||||
it('does not throw', () => {
|
||||
expect(() => UnityVersionParser.read('')).not.toThrow();
|
||||
});
|
||||
|
||||
it('reads from unity-project-with-correct-tests', () => {
|
||||
expect(UnityVersionParser.read('./unity-project-with-correct-tests')).toBe('2019.2.11f1');
|
||||
});
|
||||
});
|
||||
});
|
||||
18
src/views/results-check-details.hbs
Normal file
18
src/views/results-check-details.hbs
Normal file
@@ -0,0 +1,18 @@
|
||||
{{#runs}}
|
||||
|
||||
<details><summary>{{summary}}</summary>
|
||||
|
||||
{{#suites}}
|
||||
* {{summary}}
|
||||
{{#tests}}
|
||||
* {{summary}}
|
||||
{{#if annotation}}
|
||||
{{indent annotation.message}}
|
||||
{{indent annotation.raw_details}}
|
||||
{{/if}}
|
||||
{{/tests}}
|
||||
{{/suites}}
|
||||
|
||||
</details>
|
||||
|
||||
{{/runs}}
|
||||
3
src/views/results-check-summary.hbs
Normal file
3
src/views/results-check-summary.hbs
Normal file
@@ -0,0 +1,3 @@
|
||||
{{#runs}}
|
||||
### {{summary}}
|
||||
{{/runs}}
|
||||
71
tsconfig.json
Normal file
71
tsconfig.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
|
||||
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
// "outDir": "./", /* Redirect output structure to the directory. */
|
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true /* Skip type checking of declaration files. */,
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
},
|
||||
"include": ["src/model/ts"]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class SampleComponent : MonoBehaviour
|
||||
{
|
||||
public BasicCounter Counter;
|
||||
|
||||
void Start()
|
||||
{
|
||||
Counter = new BasicCounter(5);
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
Counter.Increment();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a5f63b9ea4b465194653c4d681faf42
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class TimerComponent : MonoBehaviour
|
||||
{
|
||||
public BasicCounter Counter = new BasicCounter();
|
||||
public float Timer = 1f;
|
||||
|
||||
void Update()
|
||||
{
|
||||
Timer -= Time.deltaTime;
|
||||
|
||||
if (Timer > 0)
|
||||
return;
|
||||
|
||||
Counter.Increment();
|
||||
Timer = 1f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b668d68a45bb48108ccda73269e3da7b
|
||||
timeCreated: 1610056748
|
||||
@@ -2,7 +2,8 @@
|
||||
"name": "PlayModeTests",
|
||||
"references": [
|
||||
"UnityEngine.TestRunner",
|
||||
"UnityEditor.TestRunner"
|
||||
"UnityEditor.TestRunner",
|
||||
"MyScripts"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
@@ -16,4 +17,4 @@
|
||||
"UNITY_INCLUDE_TESTS"
|
||||
],
|
||||
"versionDefines": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Collections;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace Tests
|
||||
{
|
||||
public class SampleComponentTest
|
||||
{
|
||||
private GameObject target;
|
||||
private SampleComponent component;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
target = GameObject.Instantiate(new GameObject());
|
||||
component = target.AddComponent<SampleComponent>();
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator TestIncrementOnUpdateAfterNextFrame()
|
||||
{
|
||||
// Save the current value, since it was updated after component Start() method called
|
||||
var count = component.Counter.Count;
|
||||
|
||||
// Skip frame and assert the new value
|
||||
yield return null;
|
||||
Assert.AreEqual(count + 1, component.Counter.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c688799d17b14d35ad515bff9de8d12c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace Tests
|
||||
@@ -12,8 +10,14 @@ namespace Tests
|
||||
[Test]
|
||||
public void NewTestScriptSimplePasses()
|
||||
{
|
||||
// Use the Assert class to test conditions
|
||||
Assert.True(true);
|
||||
// Given
|
||||
var counter = new BasicCounter(0);
|
||||
|
||||
// When
|
||||
counter.Increment();
|
||||
|
||||
// Then
|
||||
Assert.AreEqual(1, counter.Count);
|
||||
}
|
||||
|
||||
// A UnityTest behaves like a coroutine in Play Mode. In Edit Mode you can use
|
||||
@@ -21,9 +25,18 @@ namespace Tests
|
||||
[UnityTest]
|
||||
public IEnumerator NewTestScriptWithEnumeratorPasses()
|
||||
{
|
||||
// Given
|
||||
var counter = new BasicCounter(3);
|
||||
|
||||
// Use the Assert class to test conditions.
|
||||
// Use yield to skip a frame.
|
||||
yield return null;
|
||||
|
||||
// When
|
||||
counter.Increment();
|
||||
|
||||
// Then
|
||||
Assert.AreEqual(4, counter.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
using System.Collections;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace Tests
|
||||
{
|
||||
public class TimerComponentTest
|
||||
{
|
||||
private GameObject target;
|
||||
private TimerComponent component;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
target = GameObject.Instantiate(new GameObject());
|
||||
component = target.AddComponent<TimerComponent>();
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator TestIncrementAfterSomeTime()
|
||||
{
|
||||
// Save the current value, since it was updated after component Start() method called
|
||||
var count = component.Counter.Count;
|
||||
|
||||
// Skip frame and assert the new value
|
||||
yield return null;
|
||||
Assert.AreEqual(count, component.Counter.Count);
|
||||
|
||||
yield return new WaitForSeconds(1.1f);
|
||||
Assert.AreEqual(count + 1, component.Counter.Count);
|
||||
|
||||
yield return new WaitForSeconds(1.1f);
|
||||
Assert.AreEqual(count + 2, component.Counter.Count);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator TestTimeScaleIsAffectingIncrement()
|
||||
{
|
||||
// Save the current value, since it was updated after component Start() method called
|
||||
var count = component.Counter.Count;
|
||||
Time.timeScale = .5f;
|
||||
|
||||
// Skip frame and assert the new value
|
||||
yield return null;
|
||||
Assert.AreEqual(count, component.Counter.Count);
|
||||
|
||||
yield return WaitForRealSeconds(1.1f);
|
||||
Assert.AreEqual(count, component.Counter.Count);
|
||||
|
||||
yield return WaitForRealSeconds(1.1f);
|
||||
Assert.AreEqual(count + 1, component.Counter.Count);
|
||||
}
|
||||
|
||||
// Skipping time ignoring Time.scale
|
||||
// https://answers.unity.com/questions/301868/yield-waitforseconds-outside-of-timescale.html
|
||||
public static IEnumerator WaitForRealSeconds(float time)
|
||||
{
|
||||
float start = Time.realtimeSinceStartup;
|
||||
while (Time.realtimeSinceStartup < start + time)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 818b22370d404398b87b47a922a435c9
|
||||
timeCreated: 1610056889
|
||||
Reference in New Issue
Block a user