Compare commits

...

51 Commits

Author SHA1 Message Date
David Finol
215f660c06 Check GitHub token (#99)
* Reeplace createCheck option with check for githubToken

* Fix index.js
2021-02-28 00:44:56 -06:00
David Finol
43d90c252f Feature/create check (#97) 2021-02-27 12:13:19 -06:00
mob-sakai
345f4c64bd Security (#94)
* fix(test): embed unity license

* fix(test): checkout head

* fix(test): use `pull_request` event instead of `pull_request_target` event
2021-02-08 15:23:31 +01:00
Gabriel Le Breton
d45ca4403f Merge pull request #93 from game-ci/fix/readme
Update README.md
2021-02-06 20:04:03 -05:00
David Finol
7dd059111b Update README.md 2021-02-06 16:14:34 -06:00
David Finol
5edc17bb8a Fix Actions badge 2021-01-26 05:50:40 -06:00
Webber
23b6b8f5f3 Fix typo 2021-01-23 11:27:16 +01:00
Webber Takken
6e30d4827d secure license (#92)
* secure license

* Ignore runs for changes in github workflow...

... as it can become rather confusing if you try to change a workflow, but it doesn't trigger on the PR itself, but on main only.
2021-01-23 11:10:40 +01:00
Webber Takken
50e6471ee4 Expire artifacts faster (#91) 2021-01-23 10:44:31 +01:00
Webber
6a039cc828 Add cats 2021-01-23 10:39:27 +01:00
Vladimir Kryukov
2656f4e108 Cherry pick try-catch block to output results if build is failing (#90) 2021-01-17 21:55:27 +01:00
Vladimir Kryukov
e1be8325cd Add some component tests to reference unity project (#86)
* Add some component tests to reference unity project

* Use action/setup-node@v2 to avoid problems with build
2021-01-11 01:05:44 +01:00
Vladimir Kryukov
26807aaf05 Dependency bump (#88)
* Bump all dependencies to the latest versions

* Fix prettifier configuration after bump; Fix issues found by new versions of prettifier & eslint;

* Add information about yarn lint & test into CONTRIBUTING.md; Add better description of `yarn build` step in the pipeline
2021-01-11 01:05:18 +01:00
Webber Takken
a9d07b742d update deprecated (#87) 2021-01-07 23:16:29 +01:00
David Finol
0c3e710069 Get unityVersion from ProjectVersion.txt (#84) 2020-12-28 12:02:31 +01:00
mob-sakai
a067c3d5ab fix: parameter 'customImage' is not working (#83) 2020-12-17 14:30:51 +01:00
Webber Takken
3fca186a7b Fix typo 2020-12-06 18:12:42 +01:00
Webber Takken
a8b9742ecd remove anything recognizable as Unity (#82) 2020-12-06 17:45:57 +01:00
Devashish Lal
31cd755121 docker repo migrated (#77) 2020-11-26 18:19:17 +01:00
mob-sakai
679222c549 Update action.yml
Co-authored-by: Webber Takken <webber@takken.io>
2020-11-26 14:15:18 +01:00
mob-sakai
29899d84e8 feat: support custom image 2020-11-26 14:15:18 +01:00
Webber Takken
7d26e264b9 Switch to British english. 2020-11-10 18:18:18 +01:00
David LeGare
bac0f97d2f Add all parameters to action.yml
The added parameters were already supported but weren't listed, resulting in warnings for projects that use those parameters.
2020-11-10 18:18:18 +01:00
Webber
c4e44617e2 Add intructions for debugging 2020-09-27 21:37:15 +02:00
dependabot-preview[bot]
25e14f52b8 [Security] Bump node-fetch from 2.6.0 to 2.6.1
Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.6.0 to 2.6.1. **This update includes a security fix.**
- [Release notes](https://github.com/bitinn/node-fetch/releases)
- [Changelog](https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/bitinn/node-fetch/compare/v2.6.0...v2.6.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-09-12 05:30:38 +00:00
dependabot[bot]
3a40ffcf5f Bump lodash from 4.17.15 to 4.17.19
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-25 22:51:46 +02:00
Webber
3277792c11 👷 add pull request template 2020-07-12 19:04:30 +02:00
Webber
ac2e8a74d0 Fix broken babel compat data 2020-07-11 21:18:47 +02:00
Webber
8107d46ab7 Move docs to unity-ci.com 2020-07-11 21:18:47 +02:00
Collin Dauphinee
a4e8475a2f Adding useHostNetwork option 2020-04-02 12:29:34 +02:00
dependabot-preview[bot]
f02586c1c9 [Security] Bump acorn from 6.4.0 to 6.4.1
Bumps [acorn](https://github.com/acornjs/acorn) from 6.4.0 to 6.4.1. **This update includes a security fix.**
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/6.4.0...6.4.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-13 22:29:38 +00:00
Webber
31ae95179d fix dist file and improve automation 2020-03-09 23:58:40 +01:00
litefeel
cd86c7302b Apply merge request suggestions 2020-03-09 23:57:17 +01:00
litefeel
21831e61ad Fixed compatibility 2020-03-09 23:57:17 +01:00
litefeel
7c1cba9a39 Change UNITY_LICENSE_PATH to UNITY_LICENSE_FILE 2020-03-09 23:57:17 +01:00
litefeel
29e179f50b Add UNITY_LICENSE_PATH 2020-03-09 23:57:17 +01:00
dependabot-preview[bot]
2fa69494ad Bump eslint-plugin-react from 7.18.3 to 7.19.0
Bumps [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react) from 7.18.3 to 7.19.0.
- [Release notes](https://github.com/yannickcr/eslint-plugin-react/releases)
- [Changelog](https://github.com/yannickcr/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yannickcr/eslint-plugin-react/compare/v7.18.3...v7.19.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-09 22:50:45 +00:00
dependabot-preview[bot]
4e717f6c82 Bump @babel/core from 7.8.6 to 7.8.7
Bumps [@babel/core](https://github.com/babel/babel) from 7.8.6 to 7.8.7.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.8.6...v7.8.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-05 09:08:43 +00:00
dependabot-preview[bot]
173716a8a9 Bump @babel/preset-env from 7.8.6 to 7.8.7
Bumps [@babel/preset-env](https://github.com/babel/babel) from 7.8.6 to 7.8.7.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.8.6...v7.8.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-05 09:00:03 +00:00
dependabot-preview[bot]
3c915726f4 Bump @babel/preset-env from 7.8.4 to 7.8.6
Bumps [@babel/preset-env](https://github.com/babel/babel) from 7.8.4 to 7.8.6.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.8.4...v7.8.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-28 09:08:30 +00:00
dependabot-preview[bot]
5cfdb7ee17 Bump @babel/core from 7.8.4 to 7.8.6
Bumps [@babel/core](https://github.com/babel/babel) from 7.8.4 to 7.8.6.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.8.4...v7.8.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-28 09:00:06 +00:00
dependabot-preview[bot]
51648a5093 Bump babel-eslint from 10.0.3 to 10.1.0
Bumps [babel-eslint](https://github.com/babel/babel-eslint) from 10.0.3 to 10.1.0.
- [Release notes](https://github.com/babel/babel-eslint/releases)
- [Commits](https://github.com/babel/babel-eslint/compare/v10.0.3...v10.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-27 23:05:12 +00:00
dependabot-preview[bot]
2297daa673 Bump lint-staged from 10.0.7 to 10.0.8
Bumps [lint-staged](https://github.com/okonet/lint-staged) from 10.0.7 to 10.0.8.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v10.0.7...v10.0.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-26 09:00:20 +00:00
dependabot-preview[bot]
7711f454ba Bump @actions/github from 2.1.0 to 2.1.1
Bumps [@actions/github](https://github.com/actions/toolkit/tree/HEAD/packages/github) from 2.1.0 to 2.1.1.
- [Release notes](https://github.com/actions/toolkit/releases)
- [Changelog](https://github.com/actions/toolkit/blob/master/packages/github/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/github)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-21 08:41:39 +00:00
Webber
b518514f36 add dist build 2020-02-14 23:44:54 +01:00
dependabot-preview[bot]
7e50ed34dd Bump @zeit/ncc from 0.21.0 to 0.21.1
Bumps [@zeit/ncc](https://github.com/zeit/ncc) from 0.21.0 to 0.21.1.
- [Release notes](https://github.com/zeit/ncc/releases)
- [Commits](https://github.com/zeit/ncc/compare/0.21.0...0.21.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-14 23:44:54 +01:00
Alex Evgrashin
c85e37ca1f Fixed always passing action 2020-02-14 23:25:40 +01:00
Alex Evgrashin
58f580c801 Editor log now saves to artifacts folder 2020-02-13 21:58:50 +01:00
dependabot-preview[bot]
4ba71aefa9 Bump husky from 4.2.2 to 4.2.3
Bumps [husky](https://github.com/typicode/husky) from 4.2.2 to 4.2.3.
- [Release notes](https://github.com/typicode/husky/releases)
- [Changelog](https://github.com/typicode/husky/blob/master/CHANGELOG.md)
- [Commits](https://github.com/typicode/husky/compare/v4.2.2...v4.2.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-13 08:25:48 +00:00
dependabot-preview[bot]
bed834cb73 Bump eslint-plugin-unicorn from 16.0.0 to 16.1.1
Bumps [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) from 16.0.0 to 16.1.1.
- [Release notes](https://github.com/sindresorhus/eslint-plugin-unicorn/releases)
- [Commits](https://github.com/sindresorhus/eslint-plugin-unicorn/compare/v16.0.0...v16.1.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-12 09:50:49 +01:00
dependabot-preview[bot]
eb51009c09 Bump husky from 4.2.1 to 4.2.2
Bumps [husky](https://github.com/typicode/husky) from 4.2.1 to 4.2.2.
- [Release notes](https://github.com/typicode/husky/releases)
- [Changelog](https://github.com/typicode/husky/blob/master/CHANGELOG.md)
- [Commits](https://github.com/typicode/husky/compare/v4.2.1...v4.2.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-12 09:36:01 +01:00
49 changed files with 4071 additions and 2933 deletions

View File

@@ -16,6 +16,7 @@
"settings": { "react": { "version": "latest" } },
"rules": {
"prettier/prettier": "error",
"import/no-extraneous-dependencies": 0
"import/no-extraneous-dependencies": 0,
"no-underscore-dangle": 0
}
}

9
.github/pull_request_template.md vendored Normal file
View 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
View 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 }}

View File

@@ -1,10 +1,12 @@
name: Actions 😎
on:
pull_request: {}
push: { branches: [master] }
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=\"d39b8e2f4d364b2e98b06afa0c6e08c5\"/>\n <Binding Key=\"2\" Value=\"d39b8e2f4d364b2e98b06afa0c6e08c5\"/>\n </MachineBindings>\n <MachineID Value=\"Xxo1ZKbdPu/IATrc0mPBYANJFF0=\"/>\n <SerialHash Value=\"1efd68fa935192b6090ac03c77d289a9f588c55a\"/>\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=\"AQAAAEY0LUg2WFMtUE00NS1SM0M4LUUyWlotWkdWOA==\"/>\n <SerialMasked Value=\"F4-H6XS-PM45-R3C8-E2ZZ-XXXX\"/>\n <StartDate Value=\"2018-05-02T00:00:00\"/>\n <UpdateDate Value=\"2019-11-25T18:23:38\"/>\n <InitialActivationDate Value=\"2018-05-02T14:21:28\"/>\n <LicenseVersion Value=\"6.x\"/>\n <ClientProvidedVersion Value=\"2019.2.11f1\"/>\n <AlwaysOnline Value=\"false\"/>\n <Entitlements>\n <Entitlement Ns=\"unity_editor\" Tag=\"UnityPersonal\" Type=\"EDITOR\" 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>JHdOBFmBNq2H8BrGFzir/StLoYo=</DigestValue></Reference></SignedInfo><SignatureValue>aENLHd37a51RtP2/g7YU0Pexf5mx0/ENXYGtrPzqwZ8NQt2AsSdxGnl0CUB45/GuNXfJVDt2HWot\ncNYZB2OylVBn1WHQbKZlPmm8gEAMz0MYbr4Isb5i5buryBrZlmbEOjnRI+pEg1CBwlgMo6xdtjjE\n/d7cC293QIUO91kdzRXftYou1dNaUyuPL9ZH65vdB2pDXGRNxgUVD+GnnqZA7b5L2HXqNQclcWAK\n5Yd1BeF3VzR1iLw9G/SmH5oOhnpXSmqbL4qk7LVP2/mgXpFk5kP4X8VC3z47obNhBIGq40dwWyEe\nUYk5/nRAOkZawDT+tcu96e06gPC9Cxk5PdbRbA==</SignatureValue></Signature></root>"
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:
@@ -12,14 +14,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1.1.2
- 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 "action should be auto generated" ; exit 62; }
- 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 }}
@@ -35,10 +37,18 @@ jobs:
- playmode
- editmode
steps:
- uses: actions/checkout@v2
###########################
# Checkout #
###########################
- name: Checkout
uses: actions/checkout@v2
with:
lfs: true
- uses: actions/cache@v1.1.0
###########################
# Cache #
###########################
- uses: actions/cache@v2
with:
path: ${{ matrix.projectPath }}/Library
key: Library-${{ matrix.projectPath }}
@@ -52,10 +62,11 @@ jobs:
testMode: ${{ matrix.testMode }}
artifactsPath: ${{ matrix.testMode }}-artifacts
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
- uses: actions/upload-artifact@v1
- 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 ✨
@@ -68,13 +79,16 @@ jobs:
unityVersion:
- 2019.2.11f1
steps:
# Checkout repository (required to test local actions)
- name: Checkout repository
uses: actions/checkout@v2
###########################
# Checkout #
###########################
- uses: actions/checkout@v2
with:
lfs: true
# Enable caching
###########################
# Cache #
###########################
- uses: actions/cache@v1.1.0
with:
path: ${{ matrix.projectPath }}/Library
@@ -95,10 +109,11 @@ jobs:
# 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 📝
@@ -111,13 +126,16 @@ jobs:
projectPath:
- unity-project-with-correct-tests
steps:
# Checkout repository (required to test local actions)
- name: Checkout repository
uses: actions/checkout@v2
###########################
# Checkout #
###########################
- uses: actions/checkout@v2
with:
lfs: true
# Enable caching
###########################
# Cache #
###########################
- uses: actions/cache@v1.1.0
with:
path: ${{ matrix.projectPath }}/Library
@@ -138,10 +156,11 @@ jobs:
# 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 📺
@@ -154,13 +173,16 @@ jobs:
unityVersion:
- 2019.2.11f1
steps:
# Checkout repository (required to test local actions)
- name: Checkout repository
uses: actions/checkout@v2
###########################
# Checkout #
###########################
- uses: actions/checkout@v2
with:
lfs: true
# Enable caching
###########################
# Cache #
###########################
- uses: actions/cache@v1.1.0
with:
path: ${{ matrix.projectPath }}/Library
@@ -181,10 +203,11 @@ jobs:
# 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
testEachModeSequentially:
name: Test each mode sequentially 👩‍👩‍👧‍👦 # don't try this at home (it's much slower)
@@ -197,13 +220,16 @@ jobs:
projectPath:
- unity-project-with-correct-tests
steps:
# Checkout repository (required to test local actions)
- name: Checkout repository
uses: actions/checkout@v2
###########################
# Checkout #
###########################
- uses: actions/checkout@v2
with:
lfs: true
# Enable caching
###########################
# Cache #
###########################
- uses: actions/cache@v1.1.0
with:
path: ${{ matrix.projectPath }}/Library
@@ -232,7 +258,8 @@ jobs:
# 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

View File

@@ -2,5 +2,6 @@
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100
"printWidth": 100,
"arrowParens": "avoid"
}

View File

@@ -31,8 +31,8 @@ You have [Node](https://nodejs.org/) installed at v12.2.0+ and [Yarn](https://ya
Please note that commit hooks will run automatically to perform some tasks;
- format your code
- run tests
- build distributable files
- run tests & lint - `yarn lint && yarn test`
- build distributable files - `yarn build`
#### License

265
README.md
View File

@@ -1,262 +1,43 @@
# Unity - Test runner
[![Actions status](https://github.com/webbertakken/unity-test-runner/workflows/Actions%20%F0%9F%98%8E/badge.svg?event=push&branch=master)](https://github.com/webbertakken/unity-test-runner/actions?query=branch%3Amaster+event%3Apush+workflow%3A"Actions%20%F0%9F%98%8E")
---
(Not affiliated with Unity Technologies)
GitHub Action to
[run tests](https://github.com/marketplace/actions/unity-test-runner)
for any Unity project.
Part of the
[Unity Actions](https://github.com/webbertakken/unity-actions)
collection.
Part of the <a href="https://game.ci">GameCI</a> open source project.
<br />
<br />
---
[![Actions status](https://github.com/game-ci/unity-test-runner/workflows/Actions%20%F0%9F%98%8E/badge.svg)](https://github.com/game-ci/unity-test-runner/actions?query=workflow%3A%22Actions+%F0%9F%98%8E%22)
<br />
<br />
This is a recommended step to prepare your pipeline for using the
[Build](https://github.com/webbertakken/unity-actions#build)
action.
## How to use
## Documentation
Find the
[docs](https://game.ci/docs/github/test-runner)
on the GameCI
[documentation website](https://game.ci/docs).
See the
[Unity Actions](https://github.com/webbertakken/unity-actions)
collection repository for workflow documentation and reference implementation.
## Related actions
## Usage
Visit the
GameCI <a href="https://github.com/game-ci/unity-actions">Unity Actions</a>
status repository for related Actions.
#### Setup test runner
## Community
By default the test runner will run all your playmode and editmode tests.
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.
Create or edit the file called `.github/workflows/main.yml` and add a job to it.
## Contributing
##### Personal License
To help improve the documentation, please find the docs [repository](https://github.com/game-ci/documentation).
Personal licenses require a one-time manual activation step (per unity version).
Make sure you
[acquire and activate](https://github.com/marketplace/actions/unity-request-activation-file)
your license file and add it as a secret.
Then, define the test step as follows:
```yaml
- uses: webbertakken/unity-test-runner@v1.4
env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
with:
projectPath: path/to/your/project
unityVersion: 20XX.X.XXXX
```
##### Professional license
Professional licenses do not need any manual steps.
Instead, three variables will need to be set.
- `UNITY_EMAIL` (should contain the email address for your Unity account)
- `UNITY_PASSWORD` (the password that you use to login to Unity)
- `UNITY_SERIAL` (the serial provided by Unity)
Define the test step as follows:
```yaml
- uses: webbertakken/unity-test-runner@v1.4
env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
with:
projectPath: path/to/your/project
unityVersion: 20XX.X.XXXX
```
That is all you need to test your project.
#### Storing test results
To be able to access the test results,
they need to be uploaded as artifacts.
To do this it is recommended to use Github Actions official
[upload artifact action](https://github.com/marketplace/actions/upload-artifact)
after any test action.
###### Using defaults
By default, Test Runner outputs it's results to a folder named `artifacts`.
Example:
```yaml
- uses: actions/upload-artifact@v1
with:
name: Test results
path: artifacts
```
Test results can now be downloaded as Artifacts in the Actions tab.
###### Specifying artifacts folder
If a different `artifactsPath` was specified in the test runner,
you can reference this path using the `id` of the test step.
Example:
```yaml
- uses: webbertakken/unity-test-runner@v1.4
id: myTestStep
(...)
```
```yaml
- uses: actions/upload-artifact@v1
with:
name: Test results
path: ${{ steps.myTestStep.outputs.artifactsPath }}
```
#### Caching
In order to make test runs (and builds) faster,
you can cache Library files from previous runs.
To do so, simply add Github Actions' official
[cache action](https://github.com/marketplace/actions/cache)
before any unity steps.
Example:
```yaml
- uses: actions/cache@v1.1.0
with:
path: path/to/your/project/Library
key: Library-MyProjectName-TargetPlatform
restore-keys: |
Library-MyProjectName-
Library-
```
This simple addition could speed up your test runs by more than 50%.
#### Complete example
A complete workflow that tests all modes separately could look like this:
```yaml
name: Build project
on:
pull_request: {}
push: { branches: [master] }
env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
jobs:
testAllModes:
name: Test in ${{ matrix.testMode }} on version ${{ matrix.unityVersion }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
projectPath:
- path/to/your/project
unityVersion:
- 2019.2.11f1
testMode:
- playmode
- editmode
steps:
- uses: actions/checkout@v2
with:
lfs: true
- uses: actions/cache@v1.1.0
with:
path: ${{ matrix.projectPath }}/Library
key: Library-${{ matrix.projectPath }}
restore-keys: |
Library-
- uses: webbertakken/unity-test-runner@v1.4
id: tests
with:
projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }}
testMode: ${{ matrix.testMode }}
artifactsPath: ${{ matrix.testMode }}-artifacts
- uses: actions/upload-artifact@v1
with:
name: Test results for ${{ matrix.testMode }}
path: ${{ steps.tests.outputs.artifactsPath }}
```
> **Note:** _Environment variables are set for all jobs in the workflow like this._
## Configuration options
Below options can be specified under `with:` for the `unity-test-runner` action.
#### projectPath
Specify the path to your Unity project to be tested.
The path should be relative to the root of your project.
_**required:** `false`_
_**default:** `<your project root>`_
#### unityVersion
Version of Unity to use for testing the project.
_**required:** `false`_
_**default:** `2019.2.1f11`_
#### testMode
The type of tests to be run by the test runner.
Options are: "all", "playmode", "editmode"
_**required:** `false`_
_**default:** `all`_
#### artifactsPath
Path where the test results should be stored.
In this folder a folder will be created for every test mode.
_**required:** `false`_
_**default:** `artifacts`_
#### customParameters
Custom parameters to configure the test runner.
Parameters must start with a hyphen (`-`) and may be followed by a value (without hyphen).
Parameters without a value will be considered booleans (with a value of true).
_**example:**_
```yaml
- uses: webbertakken/unity-test-runner@master
with:
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
```
_**required:** `false`_
_**default:** ""_
## More actions
Visit
[Unity Actions](https://github.com/webbertakken/unity-actions)
to find related actions for Unity.
Feel free to contribute.
To contribute to this project, kindly read the [contribution guide](./CONTRIBUTING.md).
## Licence

View File

@@ -4,8 +4,38 @@ description: 'Run tests for any Unity project.'
inputs:
unityVersion:
required: false
default: '2019.2.11f1'
description: 'Version of unity to use for testing the project.'
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'

View File

@@ -8,8 +8,27 @@ source /steps/activate.sh
source /steps/run_tests.sh
source /steps/return_license.sh
#
# 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;

File diff suppressed because one or more lines are too long

View 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}}

View File

@@ -0,0 +1,3 @@
{{#runs}}
### {{summary}}
{{/runs}}

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
if [[ -n "$UNITY_LICENSE" ]]; then
if [[ -n "$UNITY_LICENSE" ]] || [[ -n "$UNITY_LICENSE_FILE" ]]; then
#
# PERSONAL LICENSE MODE
#
@@ -9,23 +9,27 @@ if [[ -n "$UNITY_LICENSE" ]]; then
# 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 `webbertakken/request-manual-activation-file` action.
# 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
# Copy license file from Github variables
echo "$UNITY_LICENSE" | tr -d '\r' > $FILE_PATH
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=$(xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' \
/opt/Unity/Editor/Unity \
-batchmode \
-nographics \
-logFile /dev/stdout \
-quit \
-manualLicenseFile $FILE_PATH)
ACTIVATION_OUTPUT=$(unity-editor \
-batchmode \
-nographics \
-logFile /dev/stdout \
-quit \
-manualLicenseFile $FILE_PATH)
# Store the exit code from the verify command
UNITY_EXIT_CODE=$?
@@ -58,15 +62,14 @@ elif [[ -n "$UNITY_SERIAL" && -n "$UNITY_EMAIL" && -n "$UNITY_PASSWORD" ]]; then
echo "Requesting activation (professional license)"
# Activate license
xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' \
/opt/Unity/Editor/Unity \
-batchmode \
-nographics \
-logFile /dev/stdout \
-quit \
-serial "$UNITY_SERIAL" \
-username "$UNITY_EMAIL" \
-password "$UNITY_PASSWORD"
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=$?
@@ -82,7 +85,7 @@ else
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 derrived.
# Immediately exit as no UNITY_EXIT_CODE can be derived.
exit 1;
fi

View File

@@ -6,11 +6,10 @@ if [[ -n "$UNITY_SERIAL" ]]; then
#
# This will return the license that is currently in use.
#
xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' \
/opt/Unity/Editor/Unity \
-batchmode \
-nographics \
-logFile /dev/stdout \
-quit \
-returnlicense
unity-editor \
-batchmode \
-nographics \
-logFile /dev/stdout \
-quit \
-returnlicense
fi

View File

@@ -74,19 +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" \
$CUSTOM_PARAMETERS
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";
@@ -109,19 +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" \
$CUSTOM_PARAMETERS
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";

View File

@@ -11,5 +11,7 @@ module.exports = {
},
},
],
'@babel/typescript',
],
plugins: ['@babel/plugin-proposal-class-properties', '@babel/proposal-object-rest-spread'],
};

View File

@@ -2,7 +2,7 @@ const esModules = ['lodash-es'].join('|');
module.exports = {
testEnvironment: 'node',
moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
transform: { '^.+\\.(js|jsx)?$': 'babel-jest' },
moduleFileExtensions: ['ts', 'js', 'jsx', 'json', 'vue'],
transform: { '^.+\\.(ts|js|jsx)?$': 'babel-jest' },
transformIgnorePatterns: [`/node_modules/(?!${esModules})`],
};

1
media/Discord-Logo.svg Normal file
View 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

View File

@@ -7,35 +7,45 @@
"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.1",
"@actions/exec": "1.0.3",
"@actions/github": "^2.0.1"
"@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.8.4",
"@babel/core": "7.8.4",
"@babel/preset-env": "7.8.4",
"@zeit/ncc": "0.21.0",
"babel-eslint": "10.0.3",
"eslint": "6.8.0",
"eslint-config-airbnb": "18.0.1",
"eslint-config-prettier": "6.10.0",
"eslint-plugin-flowtype": "4.6.0",
"eslint-plugin-import": "2.20.1",
"eslint-plugin-jsx-a11y": "6.2.3",
"eslint-plugin-prettier": "3.1.2",
"eslint-plugin-react": "7.18.3",
"eslint-plugin-unicorn": "16.0.0",
"husky": "4.2.1",
"jest": "25.1.0",
"lint-staged": "10.0.7",
"lodash-es": "4.17.15",
"prettier": "1.19.1"
"@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": {

View File

@@ -1,5 +1,5 @@
import * as core from '@actions/core';
import { Action, Docker, Input, ImageTag, Output } from './model';
import { Action, Docker, Input, ImageTag, Output, ResultsCheck } from './model';
async function action() {
Action.checkCompatibility();
@@ -7,28 +7,43 @@ async function action() {
const { dockerfile, workspace, actionFolder } = Action;
const {
unityVersion,
customImage,
projectPath,
testMode,
artifactsPath,
useHostNetwork,
customParameters,
githubToken,
checkName,
} = Input.getFromUser();
const baseImage = ImageTag.createForBase(unityVersion);
const baseImage = ImageTag.createForBase({ version: unityVersion, customImage });
// Build docker image
const actionImage = await Docker.build({ path: actionFolder, dockerfile, baseImage });
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,
customParameters,
});
// Run docker image
await Docker.run(actionImage, {
workspace,
unityVersion,
projectPath,
testMode,
artifactsPath,
useHostNetwork,
customParameters,
githubToken,
});
} finally {
// Set output
await Output.setArtifactsPath(artifactsPath);
}
// 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 => {

View File

@@ -12,7 +12,7 @@ class Docker {
--build-arg IMAGE=${baseImage} \
--tag ${tag}`;
await exec(command, null, { silent });
await exec(command, undefined, { silent });
return tag;
}
@@ -24,13 +24,16 @@ class Docker {
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 \
@@ -59,9 +62,11 @@ class Docker {
--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, null, { silent });
await exec(command, undefined, { silent });
}
}

View File

@@ -6,15 +6,20 @@ describe('Docker', () => {
it('builds', async () => {
const path = Action.actionFolder;
const dockerfile = `${path}/Dockerfile`;
const baseImage = new ImageTag({
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');
expect(tag.toString()).toStrictEqual('unity-action:3-base-0');
}, 240000);
});

View File

@@ -1,10 +1,10 @@
import { trimStart } from 'lodash-es';
class ImageTag {
static createForBase(version) {
const repository = 'gableroux';
const name = 'unity3d';
return new this({ repository, name, version });
static createForBase({ version, customImage }) {
const repository = 'unityci';
const name = 'editor';
return new this({ repository, name, version, customImage });
}
static createForAction(version) {
@@ -13,12 +13,12 @@ class ImageTag {
return new this({ repository, name, version });
}
constructor({ 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 });
Object.assign(this, { repository, name, version, customImage });
}
static get versionPattern() {
@@ -34,7 +34,11 @@ class ImageTag {
}
toString() {
return `${this.image}:${this.tag}`;
if (this.customImage && this.customImage !== '') {
return this.customImage;
}
return `${this.image}:${this.tag}-base-0`;
}
}

View File

@@ -1,6 +1,6 @@
import ImageTag from './image-tag';
describe('UnityImageVersion', () => {
describe('ImageTag', () => {
describe('constructor', () => {
const some = {
name: 'someName',
@@ -23,16 +23,25 @@ describe('UnityImageVersion', () => {
expect(() => new ImageTag({ version })).not.toThrow();
});
test.each(['some version', '', 1, null])('throws for incorrect versions %p', version => {
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('2099.1.1111');
const image = ImageTag.createForBase({ version: '2099.1.1111' });
expect(image.toString()).toStrictEqual(`gableroux/unity3d: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);
});
});
});

View File

@@ -3,5 +3,6 @@ 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 };
export { Action, Docker, Input, ImageTag, Output, ResultsCheck };

View File

@@ -1,7 +1,10 @@
import * as Index from '.';
describe('Index', () => {
test.each(['Action', 'Docker', 'ImageTag', 'Input', 'Output'])('exports %s', exportedModule => {
expect(typeof Index[exportedModule]).toStrictEqual('function');
});
test.each(['Action', 'Docker', 'ImageTag', 'Input', 'Output', 'ResultsCheck'])(
'exports %s',
exportedModule => {
expect(typeof Index[exportedModule]).toStrictEqual('function');
},
);
});

View File

@@ -1,5 +1,6 @@
import { getInput } from '@actions/core';
import { includes } from 'lodash-es';
import UnityVersionParser from './unity-version-parser';
class Input {
static get testModes() {
@@ -7,18 +8,22 @@ class Input {
}
static isValidFolderName(folderName) {
const validFolderName = new RegExp(/^(\.|\.\/)?(\.?\w+([-_]?\w+)*\/?)*$/);
const validFolderName = new RegExp(/^(\.|\.\/)?(\.?\w+([_-]?\w+)*\/?)*$/);
return validFolderName.test(folderName);
}
static getFromUser() {
// Input variables specified in workflow using "with" prop.
const unityVersion = getInput('unityVersion') || '2019.2.11f1';
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)) {
@@ -33,17 +38,28 @@ class Input {
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,
};
}
}

114
src/model/results-check.js Normal file
View File

@@ -0,0 +1,114 @@
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 (!artifactsPath || !checkName || !githubToken) {
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);
// Call GitHub API
await ResultsCheck.requestGitHubCheck(checkName, githubToken, runs, runSummary);
return runSummary.failed;
}
static async requestGitHubCheck(checkName, githubToken, runs, runSummary) {
const pullRequest = github.context.payload.pull_request;
const headSha = (pullRequest && pullRequest.head.sha) || github.context.sha;
const title = runSummary.summary;
const summary = await ResultsCheck.renderSummary(runs);
const details = await ResultsCheck.renderDetails(runs);
const rawAnnotations = runSummary.extractAnnotations();
const annotations = rawAnnotations.map(rawAnnotation => {
const annotation = rawAnnotation;
annotation.path = rawAnnotation.path.replace('/github/workspace/', '');
return annotation;
});
core.info(`Posting results for ${headSha}`);
const createCheckRequest = {
...github.context.repo,
name: checkName,
head_sha: headSha,
status: 'completed',
conclusion: 'neutral',
output: {
title,
summary,
text: details,
annotations: annotations.slice(0, 50),
},
};
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;

View 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);
});
});
});

121
src/model/results-parser.js Normal file
View File

@@ -0,0 +1,121 @@
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);
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(ResultsParser.convertSuite(run['test-suite']));
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;

View File

@@ -0,0 +1,181 @@
import ResultsParser from './results-parser';
describe('ResultsParser', () => {
describe('parseResults', () => {
it('throws for missing file', () => {
expect(() => ResultsParser.parseResults('')).rejects.toEqual(Error);
});
});
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);
});
});
});

View 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(testsToAdd: TestMeta[]): void {
testsToAdd.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 '✅';
}
}

View 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 };
}

View 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;

View 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');
});
});
});

View 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}}

View File

@@ -0,0 +1,3 @@
{{#runs}}
### {{summary}}
{{/runs}}

71
tsconfig.json Normal file
View 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"]
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7a5f63b9ea4b465194653c4d681faf42
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b668d68a45bb48108ccda73269e3da7b
timeCreated: 1610056748

View File

@@ -2,7 +2,8 @@
"name": "PlayModeTests",
"references": [
"UnityEngine.TestRunner",
"UnityEditor.TestRunner"
"UnityEditor.TestRunner",
"MyScripts"
],
"includePlatforms": [],
"excludePlatforms": [],
@@ -16,4 +17,4 @@
"UNITY_INCLUDE_TESTS"
],
"versionDefines": []
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c688799d17b14d35ad515bff9de8d12c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 818b22370d404398b87b47a922a435c9
timeCreated: 1610056889

5326
yarn.lock

File diff suppressed because it is too large Load Diff