From 4f6b2f576a2b2ce2f10314af6c29ddbc83956fdb Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Mon, 10 Apr 2023 16:23:29 +0200 Subject: [PATCH 01/32] Change the logic to support A.B.Cxx --- src/installer.ts | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/installer.ts b/src/installer.ts index ef9e993..5b4633c 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -12,7 +12,7 @@ import {IS_LINUX, IS_WINDOWS} from './utils'; import {QualityOptions} from './setup-dotnet'; export interface DotnetVersion { - type: string; + type: string | null; value: string; qualityFlag: boolean; } @@ -27,22 +27,25 @@ export class DotnetVersionResolver { } private async resolveVersionInput(): Promise { - if (!semver.validRange(this.inputVersion)) { + const isLatestPatchSyntax = /^\d+\.\d+\.\d{1}x{2}$/.test(this.inputVersion); + if (!semver.validRange(this.inputVersion) && !isLatestPatchSyntax) { throw new Error( - `'dotnet-version' was supplied in invalid format: ${this.inputVersion}! Supported syntax: A.B.C, A.B, A.B.x, A, A.x` + `'dotnet-version' was supplied in invalid format: ${this.inputVersion}! Supported syntax: A.B.C, A.B, A.B.x, A, A.x, A.B.Cxx` ); } if (semver.valid(this.inputVersion)) { this.resolvedArgument.type = 'version'; this.resolvedArgument.value = this.inputVersion; + } else if (!this.inputVersion) { + this.resolvedArgument.type = null; } else { + this.resolvedArgument.type = 'channel'; const [major, minor] = this.inputVersion.split('.'); - - if (this.isNumericTag(major)) { - this.resolvedArgument.type = 'channel'; - if (this.isNumericTag(minor)) { - this.resolvedArgument.value = `${major}.${minor}`; - } else { + if (isLatestPatchSyntax) { + this.resolvedArgument.value = this.inputVersion; + } else if (this.isNumericTag(major) && this.isNumericTag(minor)) { + this.resolvedArgument.value = `${major}.${minor}`; + } else { const httpClient = new hc.HttpClient('actions/setup-dotnet', [], { allowRetries: true, maxRetries: 3 @@ -51,7 +54,6 @@ export class DotnetVersionResolver { httpClient, [major, minor] ); - } } this.resolvedArgument.qualityFlag = +major >= 6 ? true : false; } @@ -61,11 +63,7 @@ export class DotnetVersionResolver { return /^\d+$/.test(versionTag); } - public async createDotNetVersion(): Promise<{ - type: string; - value: string; - qualityFlag: boolean; - }> { + public async createDotNetVersion(): Promise { await this.resolveVersionInput(); if (!this.resolvedArgument.type) { return this.resolvedArgument; From 660c25a3213763a17c64160616134dea8dd372f4 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Mon, 10 Apr 2023 16:24:56 +0200 Subject: [PATCH 02/32] Rebuild action --- dist/index.js | 36 ++++++++++++++++++++++-------------- src/installer.ts | 16 ++++++++-------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/dist/index.js b/dist/index.js index 8e20d7f..92fa846 100644 --- a/dist/index.js +++ b/dist/index.js @@ -250,27 +250,35 @@ class DotnetVersionResolver { } resolveVersionInput() { return __awaiter(this, void 0, void 0, function* () { - if (!semver_1.default.validRange(this.inputVersion)) { - throw new Error(`'dotnet-version' was supplied in invalid format: ${this.inputVersion}! Supported syntax: A.B.C, A.B, A.B.x, A, A.x`); + const isLatestPatchSyntax = /^\d+\.\d+\.\d{1}x{2}$/.test(this.inputVersion); + if (!semver_1.default.validRange(this.inputVersion) && !isLatestPatchSyntax) { + throw new Error(`'dotnet-version' was supplied in invalid format: ${this.inputVersion}! Supported syntax: A.B.C, A.B, A.B.x, A, A.x, A.B.Cxx`); } if (semver_1.default.valid(this.inputVersion)) { this.resolvedArgument.type = 'version'; this.resolvedArgument.value = this.inputVersion; } + else if (!this.inputVersion) { + this.resolvedArgument.type = null; + } else { + this.resolvedArgument.type = 'channel'; const [major, minor] = this.inputVersion.split('.'); - if (this.isNumericTag(major)) { - this.resolvedArgument.type = 'channel'; - if (this.isNumericTag(minor)) { - this.resolvedArgument.value = `${major}.${minor}`; - } - else { - const httpClient = new hc.HttpClient('actions/setup-dotnet', [], { - allowRetries: true, - maxRetries: 3 - }); - this.resolvedArgument.value = yield this.getLatestVersion(httpClient, [major, minor]); - } + if (isLatestPatchSyntax) { + this.resolvedArgument.value = this.inputVersion; + } + else if (this.isNumericTag(major) && this.isNumericTag(minor)) { + this.resolvedArgument.value = `${major}.${minor}`; + } + else { + const httpClient = new hc.HttpClient('actions/setup-dotnet', [], { + allowRetries: true, + maxRetries: 3 + }); + this.resolvedArgument.value = yield this.getLatestVersion(httpClient, [ + major, + minor + ]); } this.resolvedArgument.qualityFlag = +major >= 6 ? true : false; } diff --git a/src/installer.ts b/src/installer.ts index 5b4633c..3764766 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -46,14 +46,14 @@ export class DotnetVersionResolver { } else if (this.isNumericTag(major) && this.isNumericTag(minor)) { this.resolvedArgument.value = `${major}.${minor}`; } else { - const httpClient = new hc.HttpClient('actions/setup-dotnet', [], { - allowRetries: true, - maxRetries: 3 - }); - this.resolvedArgument.value = await this.getLatestVersion( - httpClient, - [major, minor] - ); + const httpClient = new hc.HttpClient('actions/setup-dotnet', [], { + allowRetries: true, + maxRetries: 3 + }); + this.resolvedArgument.value = await this.getLatestVersion(httpClient, [ + major, + minor + ]); } this.resolvedArgument.qualityFlag = +major >= 6 ? true : false; } From f199d27aa16f8e90965db2b0d490f64f00b05178 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Mon, 10 Apr 2023 16:58:35 +0200 Subject: [PATCH 03/32] Update solution --- dist/index.js | 3 --- src/installer.ts | 4 +--- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/dist/index.js b/dist/index.js index 92fa846..423a712 100644 --- a/dist/index.js +++ b/dist/index.js @@ -258,9 +258,6 @@ class DotnetVersionResolver { this.resolvedArgument.type = 'version'; this.resolvedArgument.value = this.inputVersion; } - else if (!this.inputVersion) { - this.resolvedArgument.type = null; - } else { this.resolvedArgument.type = 'channel'; const [major, minor] = this.inputVersion.split('.'); diff --git a/src/installer.ts b/src/installer.ts index 3764766..62b85d4 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -12,7 +12,7 @@ import {IS_LINUX, IS_WINDOWS} from './utils'; import {QualityOptions} from './setup-dotnet'; export interface DotnetVersion { - type: string | null; + type: string; value: string; qualityFlag: boolean; } @@ -36,8 +36,6 @@ export class DotnetVersionResolver { if (semver.valid(this.inputVersion)) { this.resolvedArgument.type = 'version'; this.resolvedArgument.value = this.inputVersion; - } else if (!this.inputVersion) { - this.resolvedArgument.type = null; } else { this.resolvedArgument.type = 'channel'; const [major, minor] = this.inputVersion.split('.'); From 0318091611d2eafad5e6d5f2d63c36cb06599242 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Tue, 11 Apr 2023 13:20:34 +0200 Subject: [PATCH 04/32] Update resolveVersionInput() --- dist/index.js | 19 ++++++++----------- src/installer.ts | 24 ++++++++---------------- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/dist/index.js b/dist/index.js index 423a712..6a33cee 100644 --- a/dist/index.js +++ b/dist/index.js @@ -268,14 +268,7 @@ class DotnetVersionResolver { this.resolvedArgument.value = `${major}.${minor}`; } else { - const httpClient = new hc.HttpClient('actions/setup-dotnet', [], { - allowRetries: true, - maxRetries: 3 - }); - this.resolvedArgument.value = yield this.getLatestVersion(httpClient, [ - major, - minor - ]); + this.resolvedArgument.value = yield this.getLatestByMajorTag(major); } this.resolvedArgument.qualityFlag = +major >= 6 ? true : false; } @@ -301,17 +294,21 @@ class DotnetVersionResolver { return this.resolvedArgument; }); } - getLatestVersion(httpClient, versionParts) { + getLatestByMajorTag(majorTag) { return __awaiter(this, void 0, void 0, function* () { + const httpClient = new hc.HttpClient('actions/setup-dotnet', [], { + allowRetries: true, + maxRetries: 3 + }); const response = yield httpClient.getJson(DotnetVersionResolver.DotNetCoreIndexUrl); const result = response.result || {}; const releasesInfo = result['releases-index']; const releaseInfo = releasesInfo.find(info => { const sdkParts = info['channel-version'].split('.'); - return sdkParts[0] === versionParts[0]; + return sdkParts[0] === majorTag; }); if (!releaseInfo) { - throw new Error(`Could not find info for version ${versionParts.join('.')} at ${DotnetVersionResolver.DotNetCoreIndexUrl}`); + throw new Error(`Could not find info for version with major tag: v${majorTag} at ${DotnetVersionResolver.DotNetCoreIndexUrl}`); } return releaseInfo['channel-version']; }); diff --git a/src/installer.ts b/src/installer.ts index 62b85d4..ec89a79 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -44,14 +44,7 @@ export class DotnetVersionResolver { } else if (this.isNumericTag(major) && this.isNumericTag(minor)) { this.resolvedArgument.value = `${major}.${minor}`; } else { - const httpClient = new hc.HttpClient('actions/setup-dotnet', [], { - allowRetries: true, - maxRetries: 3 - }); - this.resolvedArgument.value = await this.getLatestVersion(httpClient, [ - major, - minor - ]); + this.resolvedArgument.value = await this.getLatestByMajorTag(major); } this.resolvedArgument.qualityFlag = +major >= 6 ? true : false; } @@ -76,10 +69,11 @@ export class DotnetVersionResolver { return this.resolvedArgument; } - private async getLatestVersion( - httpClient: hc.HttpClient, - versionParts: string[] - ): Promise { + private async getLatestByMajorTag(majorTag: string): Promise { + const httpClient = new hc.HttpClient('actions/setup-dotnet', [], { + allowRetries: true, + maxRetries: 3 + }); const response = await httpClient.getJson( DotnetVersionResolver.DotNetCoreIndexUrl ); @@ -88,14 +82,12 @@ export class DotnetVersionResolver { const releaseInfo = releasesInfo.find(info => { const sdkParts: string[] = info['channel-version'].split('.'); - return sdkParts[0] === versionParts[0]; + return sdkParts[0] === majorTag; }); if (!releaseInfo) { throw new Error( - `Could not find info for version ${versionParts.join('.')} at ${ - DotnetVersionResolver.DotNetCoreIndexUrl - }` + `Could not find info for version with major tag: v${majorTag} at ${DotnetVersionResolver.DotNetCoreIndexUrl}` ); } From 12f70884d768a07fbd0e6c3566651161a817b92e Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Tue, 11 Apr 2023 13:23:50 +0200 Subject: [PATCH 05/32] Fix typo --- dist/index.js | 2 +- src/installer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/index.js b/dist/index.js index 6a33cee..0515933 100644 --- a/dist/index.js +++ b/dist/index.js @@ -308,7 +308,7 @@ class DotnetVersionResolver { return sdkParts[0] === majorTag; }); if (!releaseInfo) { - throw new Error(`Could not find info for version with major tag: v${majorTag} at ${DotnetVersionResolver.DotNetCoreIndexUrl}`); + throw new Error(`Could not find info for version with major tag: ${majorTag} at ${DotnetVersionResolver.DotNetCoreIndexUrl}`); } return releaseInfo['channel-version']; }); diff --git a/src/installer.ts b/src/installer.ts index ec89a79..2fad685 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -87,7 +87,7 @@ export class DotnetVersionResolver { if (!releaseInfo) { throw new Error( - `Could not find info for version with major tag: v${majorTag} at ${DotnetVersionResolver.DotNetCoreIndexUrl}` + `Could not find info for version with major tag: ${majorTag} at ${DotnetVersionResolver.DotNetCoreIndexUrl}` ); } From aa34a3ceaa698ef02004c91a4fc3bca128678557 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Tue, 11 Apr 2023 13:44:35 +0200 Subject: [PATCH 06/32] Fix typo --- dist/index.js | 2 +- src/installer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/index.js b/dist/index.js index 0515933..bc00897 100644 --- a/dist/index.js +++ b/dist/index.js @@ -308,7 +308,7 @@ class DotnetVersionResolver { return sdkParts[0] === majorTag; }); if (!releaseInfo) { - throw new Error(`Could not find info for version with major tag: ${majorTag} at ${DotnetVersionResolver.DotNetCoreIndexUrl}`); + throw new Error(`Could not find info for version with major tag: "${majorTag}" at ${DotnetVersionResolver.DotNetCoreIndexUrl}`); } return releaseInfo['channel-version']; }); diff --git a/src/installer.ts b/src/installer.ts index 2fad685..22fa100 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -87,7 +87,7 @@ export class DotnetVersionResolver { if (!releaseInfo) { throw new Error( - `Could not find info for version with major tag: ${majorTag} at ${DotnetVersionResolver.DotNetCoreIndexUrl}` + `Could not find info for version with major tag: "${majorTag}" at ${DotnetVersionResolver.DotNetCoreIndexUrl}` ); } From 5f570676c249d99788f868b377a4e76fc46b28c6 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Tue, 11 Apr 2023 15:53:11 +0200 Subject: [PATCH 07/32] Add unit and e2e tests --- .github/workflows/e2e-tests.yml | 24 ++++++++++++++++++++++++ __tests__/installer.test.ts | 5 +++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 1be4173..36fd1ef 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -165,6 +165,30 @@ jobs: shell: pwsh run: __tests__/verify-dotnet.ps1 3.1 2.2 + test-setup-with-ABCxx-syntax: + runs-on: ${{ matrix.operating-system }} + strategy: + fail-fast: false + matrix: + operating-system: [ubuntu-latest, windows-latest, macOS-latest] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Clear toolcache + shell: pwsh + run: __tests__/clear-toolcache.ps1 ${{ runner.os }} + - name: Setup dotnet '3.1.1xx' + uses: ./ + with: + dotnet-version: '3.1.1xx' + - name: Setup dotnet '6.0.3xx' + uses: ./ + with: + dotnet-version: '6.0.3xx' + - name: Verify dotnet + shell: pwsh + run: __tests__/verify-dotnet.ps1 3.1.1 6.0.3 + test-setup-global-json-specified-and-version: runs-on: ${{ matrix.operating-system }} strategy: diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 1a7e024..6c20afe 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -201,6 +201,7 @@ describe('DotnetVersionResolver tests', () => { '.2.3', '.2.x', '*.', + '*', '1.2.', '1.2.-abc', 'a.b', @@ -221,7 +222,7 @@ describe('DotnetVersionResolver tests', () => { } ); - each(['3.1', '3.1.x', '3.1.*', '3.1.X']).test( + each(['3.1', '3.1.x', '3.1.*', '3.1.X', '3.1.1xx']).test( "if version: '%s' that can be resolved to 'channel' option is supplied, it should set type to 'channel' in version object", async version => { const dotnetVersionResolver = new installer.DotnetVersionResolver( @@ -233,7 +234,7 @@ describe('DotnetVersionResolver tests', () => { } ); - each(['6.0', '6.0.x', '6.0.*', '6.0.X']).test( + each(['6.0', '6.0.x', '6.0.*', '6.0.X', '6.0.1xx']).test( "if version: '%s' that can be resolved to 'channel' option is supplied and its major tag is >= 6, it should set type to 'channel' and qualityFlag to 'true' in version object", async version => { const dotnetVersionResolver = new installer.DotnetVersionResolver( From 34c30d0e8188386834a5bb496c316cdeba7658c0 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Wed, 12 Apr 2023 15:44:03 +0200 Subject: [PATCH 08/32] Refactor logic --- .github/workflows/e2e-tests.yml | 10 +++---- __tests__/installer.test.ts | 3 +- dist/index.js | 50 ++++++++++++++++++++++----------- src/installer.ts | 49 +++++++++++++++++++++++--------- 4 files changed, 75 insertions(+), 37 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 36fd1ef..f3588e8 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -177,17 +177,17 @@ jobs: - name: Clear toolcache shell: pwsh run: __tests__/clear-toolcache.ps1 ${{ runner.os }} - - name: Setup dotnet '3.1.1xx' + - name: Setup dotnet '5.0.1xx' uses: ./ with: - dotnet-version: '3.1.1xx' - - name: Setup dotnet '6.0.3xx' + dotnet-version: '5.0.1xx' + - name: Setup dotnet '7.0.1xx' uses: ./ with: - dotnet-version: '6.0.3xx' + dotnet-version: '7.0.1xx' - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 3.1.1 6.0.3 + run: __tests__/verify-dotnet.ps1 5.0.1 7.0.1 test-setup-global-json-specified-and-version: runs-on: ${{ matrix.operating-system }} diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 6c20afe..363310c 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -201,7 +201,6 @@ describe('DotnetVersionResolver tests', () => { '.2.3', '.2.x', '*.', - '*', '1.2.', '1.2.-abc', 'a.b', @@ -222,7 +221,7 @@ describe('DotnetVersionResolver tests', () => { } ); - each(['3.1', '3.1.x', '3.1.*', '3.1.X', '3.1.1xx']).test( + each(['3.1', '3.1.x', '3.1.*', '3.1.X', '5.0.1xx']).test( "if version: '%s' that can be resolved to 'channel' option is supplied, it should set type to 'channel' in version object", async version => { const dotnetVersionResolver = new installer.DotnetVersionResolver( diff --git a/dist/index.js b/dist/index.js index bc00897..ed522cc 100644 --- a/dist/index.js +++ b/dist/index.js @@ -250,33 +250,51 @@ class DotnetVersionResolver { } resolveVersionInput() { return __awaiter(this, void 0, void 0, function* () { - const isLatestPatchSyntax = /^\d+\.\d+\.\d{1}x{2}$/.test(this.inputVersion); - if (!semver_1.default.validRange(this.inputVersion) && !isLatestPatchSyntax) { + if (!semver_1.default.validRange(this.inputVersion) && !this.isLatestPatchSyntax()) { throw new Error(`'dotnet-version' was supplied in invalid format: ${this.inputVersion}! Supported syntax: A.B.C, A.B, A.B.x, A, A.x, A.B.Cxx`); } if (semver_1.default.valid(this.inputVersion)) { - this.resolvedArgument.type = 'version'; - this.resolvedArgument.value = this.inputVersion; + this.createVersionArgument(); } else { - this.resolvedArgument.type = 'channel'; - const [major, minor] = this.inputVersion.split('.'); - if (isLatestPatchSyntax) { - this.resolvedArgument.value = this.inputVersion; - } - else if (this.isNumericTag(major) && this.isNumericTag(minor)) { - this.resolvedArgument.value = `${major}.${minor}`; - } - else { - this.resolvedArgument.value = yield this.getLatestByMajorTag(major); - } - this.resolvedArgument.qualityFlag = +major >= 6 ? true : false; + yield this.createChannelArgument(); } }); } isNumericTag(versionTag) { return /^\d+$/.test(versionTag); } + isLatestPatchSyntax() { + var _b, _c; + const majorTag = (_c = (_b = this.inputVersion.match(/^(?\d+)\.\d+\.\d{1}x{2}$/)) === null || _b === void 0 ? void 0 : _b.groups) === null || _c === void 0 ? void 0 : _c.majorTag; + if (majorTag && parseInt(majorTag) < 5) { + throw new Error(`'dotnet-version' was supplied in invalid format: ${this.inputVersion}! The A.B.Cxx syntax is available since the .NET 5.0 release.`); + } + return majorTag ? true : false; + } + createVersionArgument() { + this.resolvedArgument.type = 'version'; + this.resolvedArgument.value = this.inputVersion; + } + createChannelArgument() { + return __awaiter(this, void 0, void 0, function* () { + this.resolvedArgument.type = 'channel'; + const [major, minor] = this.inputVersion.split('.'); + if (this.isLatestPatchSyntax()) { + this.resolvedArgument.value = this.inputVersion; + } + else if (this.isNumericTag(major) && this.isNumericTag(minor)) { + this.resolvedArgument.value = `${major}.${minor}`; + } + else if (this.isNumericTag(major)) { + this.resolvedArgument.value = yield this.getLatestByMajorTag(major); + } + else { + this.resolvedArgument.value = 'LTS'; + } + this.resolvedArgument.qualityFlag = +major >= 6 ? true : false; + }); + } createDotNetVersion() { return __awaiter(this, void 0, void 0, function* () { yield this.resolveVersionInput(); diff --git a/src/installer.ts b/src/installer.ts index 22fa100..9b85f7b 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -27,26 +27,15 @@ export class DotnetVersionResolver { } private async resolveVersionInput(): Promise { - const isLatestPatchSyntax = /^\d+\.\d+\.\d{1}x{2}$/.test(this.inputVersion); - if (!semver.validRange(this.inputVersion) && !isLatestPatchSyntax) { + if (!semver.validRange(this.inputVersion) && !this.isLatestPatchSyntax()) { throw new Error( `'dotnet-version' was supplied in invalid format: ${this.inputVersion}! Supported syntax: A.B.C, A.B, A.B.x, A, A.x, A.B.Cxx` ); } if (semver.valid(this.inputVersion)) { - this.resolvedArgument.type = 'version'; - this.resolvedArgument.value = this.inputVersion; + this.createVersionArgument(); } else { - this.resolvedArgument.type = 'channel'; - const [major, minor] = this.inputVersion.split('.'); - if (isLatestPatchSyntax) { - this.resolvedArgument.value = this.inputVersion; - } else if (this.isNumericTag(major) && this.isNumericTag(minor)) { - this.resolvedArgument.value = `${major}.${minor}`; - } else { - this.resolvedArgument.value = await this.getLatestByMajorTag(major); - } - this.resolvedArgument.qualityFlag = +major >= 6 ? true : false; + await this.createChannelArgument(); } } @@ -54,6 +43,38 @@ export class DotnetVersionResolver { return /^\d+$/.test(versionTag); } + private isLatestPatchSyntax() { + const majorTag = this.inputVersion.match( + /^(?\d+)\.\d+\.\d{1}x{2}$/ + )?.groups?.majorTag; + if (majorTag && parseInt(majorTag) < 5) { + throw new Error( + `'dotnet-version' was supplied in invalid format: ${this.inputVersion}! The A.B.Cxx syntax is available since the .NET 5.0 release.` + ); + } + return majorTag ? true : false; + } + + private createVersionArgument() { + this.resolvedArgument.type = 'version'; + this.resolvedArgument.value = this.inputVersion; + } + + private async createChannelArgument() { + this.resolvedArgument.type = 'channel'; + const [major, minor] = this.inputVersion.split('.'); + if (this.isLatestPatchSyntax()) { + this.resolvedArgument.value = this.inputVersion; + } else if (this.isNumericTag(major) && this.isNumericTag(minor)) { + this.resolvedArgument.value = `${major}.${minor}`; + } else if (this.isNumericTag(major)) { + this.resolvedArgument.value = await this.getLatestByMajorTag(major); + } else { + this.resolvedArgument.value = 'LTS'; + } + this.resolvedArgument.qualityFlag = +major >= 6 ? true : false; + } + public async createDotNetVersion(): Promise { await this.resolveVersionInput(); if (!this.resolvedArgument.type) { From 7358a445904166c3f83f0d04320d96f189925490 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Wed, 12 Apr 2023 16:44:51 +0200 Subject: [PATCH 09/32] Update unit tests --- .github/workflows/e2e-tests.yml | 6 +++--- dist/index.js | 3 ++- src/installer.ts | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index f3588e8..551ad8c 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -177,17 +177,17 @@ jobs: - name: Clear toolcache shell: pwsh run: __tests__/clear-toolcache.ps1 ${{ runner.os }} - - name: Setup dotnet '5.0.1xx' + - name: Setup dotnet '5.0.2xx' uses: ./ with: - dotnet-version: '5.0.1xx' + dotnet-version: '5.0.2xx' - name: Setup dotnet '7.0.1xx' uses: ./ with: dotnet-version: '7.0.1xx' - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 5.0.1 7.0.1 + run: __tests__/verify-dotnet.ps1 5.0.2 7.0.1 test-setup-global-json-specified-and-version: runs-on: ${{ matrix.operating-system }} diff --git a/dist/index.js b/dist/index.js index ed522cc..a9e8a7e 100644 --- a/dist/index.js +++ b/dist/index.js @@ -290,9 +290,10 @@ class DotnetVersionResolver { this.resolvedArgument.value = yield this.getLatestByMajorTag(major); } else { + // Resolve LTS version of .NET if "dotnet-version" is specified as *, x or X this.resolvedArgument.value = 'LTS'; } - this.resolvedArgument.qualityFlag = +major >= 6 ? true : false; + this.resolvedArgument.qualityFlag = parseInt(major) >= 6 ? true : false; }); } createDotNetVersion() { diff --git a/src/installer.ts b/src/installer.ts index 9b85f7b..211c6c7 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -70,9 +70,10 @@ export class DotnetVersionResolver { } else if (this.isNumericTag(major)) { this.resolvedArgument.value = await this.getLatestByMajorTag(major); } else { + // Resolve LTS version of .NET if "dotnet-version" is specified as *, x or X this.resolvedArgument.value = 'LTS'; } - this.resolvedArgument.qualityFlag = +major >= 6 ? true : false; + this.resolvedArgument.qualityFlag = parseInt(major) >= 6 ? true : false; } public async createDotNetVersion(): Promise { From 559e47b01b20368e2cfc1fc8975b5ffe267f5c89 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Thu, 13 Apr 2023 10:33:52 +0200 Subject: [PATCH 10/32] Fix review points --- .github/workflows/e2e-tests.yml | 24 ------------------------ dist/index.js | 16 +++++++++++++--- src/installer.ts | 18 +++++++++++++++--- 3 files changed, 28 insertions(+), 30 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 551ad8c..1be4173 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -165,30 +165,6 @@ jobs: shell: pwsh run: __tests__/verify-dotnet.ps1 3.1 2.2 - test-setup-with-ABCxx-syntax: - runs-on: ${{ matrix.operating-system }} - strategy: - fail-fast: false - matrix: - operating-system: [ubuntu-latest, windows-latest, macOS-latest] - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Clear toolcache - shell: pwsh - run: __tests__/clear-toolcache.ps1 ${{ runner.os }} - - name: Setup dotnet '5.0.2xx' - uses: ./ - with: - dotnet-version: '5.0.2xx' - - name: Setup dotnet '7.0.1xx' - uses: ./ - with: - dotnet-version: '7.0.1xx' - - name: Verify dotnet - shell: pwsh - run: __tests__/verify-dotnet.ps1 5.0.2 7.0.1 - test-setup-global-json-specified-and-version: runs-on: ${{ matrix.operating-system }} strategy: diff --git a/dist/index.js b/dist/index.js index a9e8a7e..40338d4 100644 --- a/dist/index.js +++ b/dist/index.js @@ -243,6 +243,11 @@ const path_1 = __importDefault(__nccwpck_require__(1017)); const os_1 = __importDefault(__nccwpck_require__(2037)); const semver_1 = __importDefault(__nccwpck_require__(5911)); const utils_1 = __nccwpck_require__(918); +var DotnetInstallerLimits; +(function (DotnetInstallerLimits) { + DotnetInstallerLimits[DotnetInstallerLimits["QualityInputMinimalMajorTag"] = 6] = "QualityInputMinimalMajorTag"; + DotnetInstallerLimits[DotnetInstallerLimits["LatestPatchSyntaxMinimalMajorTag"] = 5] = "LatestPatchSyntaxMinimalMajorTag"; +})(DotnetInstallerLimits || (DotnetInstallerLimits = {})); class DotnetVersionResolver { constructor(version) { this.inputVersion = version.trim(); @@ -267,7 +272,9 @@ class DotnetVersionResolver { isLatestPatchSyntax() { var _b, _c; const majorTag = (_c = (_b = this.inputVersion.match(/^(?\d+)\.\d+\.\d{1}x{2}$/)) === null || _b === void 0 ? void 0 : _b.groups) === null || _c === void 0 ? void 0 : _c.majorTag; - if (majorTag && parseInt(majorTag) < 5) { + if (majorTag && + parseInt(majorTag) < + DotnetInstallerLimits.LatestPatchSyntaxMinimalMajorTag) { throw new Error(`'dotnet-version' was supplied in invalid format: ${this.inputVersion}! The A.B.Cxx syntax is available since the .NET 5.0 release.`); } return majorTag ? true : false; @@ -290,10 +297,13 @@ class DotnetVersionResolver { this.resolvedArgument.value = yield this.getLatestByMajorTag(major); } else { - // Resolve LTS version of .NET if "dotnet-version" is specified as *, x or X + // If "dotnet-version" is specified as *, x or X resolve latest version of .NET explicitly from LTS channel. The version argument will be set to "latest" by default. this.resolvedArgument.value = 'LTS'; } - this.resolvedArgument.qualityFlag = parseInt(major) >= 6 ? true : false; + this.resolvedArgument.qualityFlag = + parseInt(major) >= DotnetInstallerLimits.QualityInputMinimalMajorTag + ? true + : false; }); } createDotNetVersion() { diff --git a/src/installer.ts b/src/installer.ts index 211c6c7..a105fef 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -17,6 +17,11 @@ export interface DotnetVersion { qualityFlag: boolean; } +enum DotnetInstallerLimits { + QualityInputMinimalMajorTag = 6, + LatestPatchSyntaxMinimalMajorTag = 5 +} + export class DotnetVersionResolver { private inputVersion: string; private resolvedArgument: DotnetVersion; @@ -47,7 +52,11 @@ export class DotnetVersionResolver { const majorTag = this.inputVersion.match( /^(?\d+)\.\d+\.\d{1}x{2}$/ )?.groups?.majorTag; - if (majorTag && parseInt(majorTag) < 5) { + if ( + majorTag && + parseInt(majorTag) < + DotnetInstallerLimits.LatestPatchSyntaxMinimalMajorTag + ) { throw new Error( `'dotnet-version' was supplied in invalid format: ${this.inputVersion}! The A.B.Cxx syntax is available since the .NET 5.0 release.` ); @@ -70,10 +79,13 @@ export class DotnetVersionResolver { } else if (this.isNumericTag(major)) { this.resolvedArgument.value = await this.getLatestByMajorTag(major); } else { - // Resolve LTS version of .NET if "dotnet-version" is specified as *, x or X + // If "dotnet-version" is specified as *, x or X resolve latest version of .NET explicitly from LTS channel. The version argument will be set to "latest" by default. this.resolvedArgument.value = 'LTS'; } - this.resolvedArgument.qualityFlag = parseInt(major) >= 6 ? true : false; + this.resolvedArgument.qualityFlag = + parseInt(major) >= DotnetInstallerLimits.QualityInputMinimalMajorTag + ? true + : false; } public async createDotNetVersion(): Promise { From b72f430d3615f3631ecde8d268101c3804566f79 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Thu, 13 Apr 2023 17:28:59 +0200 Subject: [PATCH 11/32] Update e2e tests --- .github/workflows/e2e-tests.yml | 37 +++---- __tests__/e2e-test-csproj/Test.cs | 14 +++ __tests__/e2e-test-csproj/test.csproj | 15 +++ __tests__/sample-csproj/Program.cs | 15 --- __tests__/sample-csproj/sample.csproj | 18 ---- __tests__/verify-dotnet.ps1 | 137 +++++++++++++++----------- 6 files changed, 123 insertions(+), 113 deletions(-) create mode 100644 __tests__/e2e-test-csproj/Test.cs create mode 100644 __tests__/e2e-test-csproj/test.csproj delete mode 100644 __tests__/sample-csproj/Program.cs delete mode 100644 __tests__/sample-csproj/sample.csproj diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 1be4173..fd6e85b 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -33,7 +33,7 @@ jobs: 3.0.x - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 2.2.402 3.1.404 '3.0' + run: __tests__/verify-dotnet.ps1 -Patterns 2.2.402, 3.1.404, 3.0 test-setup-full-version: runs-on: ${{ matrix.operating-system }} @@ -60,13 +60,9 @@ jobs: source-url: https://api.nuget.org/v3/index.json env: NUGET_AUTH_TOKEN: NOTATOKEN - - name: Verify nuget config file - shell: pwsh - run: | - if (-Not (Test-Path "../nuget.config")) { throw "nuget file not generated correctly" } - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 3.1.201 2.2.402 + run: __tests__/verify-dotnet.ps1 -Patterns 3.1.201, 2.2.402 -CheckNugetConfig test-setup-without-patch-version: runs-on: ${{ matrix.operating-system }} @@ -91,7 +87,7 @@ jobs: dotnet-version: '2.2' - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 3.1 2.2 + run: __tests__/verify-dotnet.ps1 -Patterns 3.1, 2.2 test-setup-prerelease-version: runs-on: ${{ matrix.operating-system }} @@ -105,17 +101,13 @@ jobs: - name: Clear toolcache shell: pwsh run: __tests__/clear-toolcache.ps1 ${{ runner.os }} - - name: Setup dotnet '2.2' - uses: ./ - with: - dotnet-version: '2.2' - name: Setup dotnet '3.1.100-preview1-014459' uses: ./ with: dotnet-version: '3.1.100-preview1-014459' - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 3.1.100-preview1-014459 + run: __tests__/verify-dotnet.ps1 -Patterns 3.1.100-preview1-014459 test-setup-latest-patch-version: runs-on: ${{ matrix.operating-system }} @@ -139,7 +131,7 @@ jobs: dotnet-version: 2.2.X - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 '2.2' '3.1' + run: __tests__/verify-dotnet.ps1 -Patterns 2.2, 3.1 test-setup-with-wildcard: runs-on: ${{ matrix.operating-system }} @@ -163,7 +155,7 @@ jobs: dotnet-version: 2.2.* - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 3.1 2.2 + run: __tests__/verify-dotnet.ps1 -Patterns 3.1, 2.2 test-setup-global-json-specified-and-version: runs-on: ${{ matrix.operating-system }} @@ -181,7 +173,7 @@ jobs: shell: bash run: | mkdir subdirectory - echo '{"sdk":{"version": "2.2","rollForward": "latestFeature"}}' > ./subdirectory/global.json + echo '{"sdk":{"version": "2.2.207","rollForward": "latestFeature"}}' > ./subdirectory/global.json - name: Setup dotnet uses: ./ with: @@ -189,7 +181,7 @@ jobs: global-json-file: ./subdirectory/global.json - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 2.2 3.1 + run: __tests__/verify-dotnet.ps1 -Patterns 2.2.207, 3.1 test-setup-with-dotnet-quality: runs-on: ${{ matrix.operating-system }} @@ -209,12 +201,9 @@ jobs: with: dotnet-version: '7.0' dotnet-quality: 'preview' - - name: Verify preview version + - name: Verify dotnet shell: pwsh - run: | - $version = & dotnet --version - Write-Host "Installed version: $version" - if (-not ($version.Contains("preview") -or $version.Contains("rc"))) { throw "Unexpected version" } + run: __tests__/verify-dotnet.ps1 -Patterns 7.0 -ContainedPattern "preview" test-dotnet-version-output-during-single-version-installation: runs-on: ${{ matrix.operating-system }} @@ -300,7 +289,8 @@ jobs: env: NUGET_AUTH_TOKEN: NOTATOKEN - name: Verify dotnet - run: __tests__/verify-dotnet.sh 3.1.201 + shell: pwsh + run: __tests__/verify-dotnet.ps1 -Patterns 3.1.201 -CheckNugetConfig test-bypass-proxy: runs-on: ubuntu-latest @@ -320,4 +310,5 @@ jobs: env: NUGET_AUTH_TOKEN: NOTATOKEN - name: Verify dotnet - run: __tests__/verify-dotnet.sh 3.1.201 + shell: pwsh + run: __tests__/verify-dotnet.ps1 -Patterns 3.1.201 -CheckNugetConfig diff --git a/__tests__/e2e-test-csproj/Test.cs b/__tests__/e2e-test-csproj/Test.cs new file mode 100644 index 0000000..533bdb8 --- /dev/null +++ b/__tests__/e2e-test-csproj/Test.cs @@ -0,0 +1,14 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace test_csproj +{ + [TestClass] + public class Test + { + [TestMethod] + public void TestMethod() + { + Assert.AreEqual((1 + 1), 2); + } + } +} diff --git a/__tests__/e2e-test-csproj/test.csproj b/__tests__/e2e-test-csproj/test.csproj new file mode 100644 index 0000000..370186a --- /dev/null +++ b/__tests__/e2e-test-csproj/test.csproj @@ -0,0 +1,15 @@ + + + + $(TEST_TARGET_FRAMEWORK) + + false + + + + + + + + + diff --git a/__tests__/sample-csproj/Program.cs b/__tests__/sample-csproj/Program.cs deleted file mode 100644 index f14c939..0000000 --- a/__tests__/sample-csproj/Program.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; - -namespace sample_csproj -{ - [TestClass] - public class Program - { - [TestMethod] - public void TestMethod1() - { - Console.WriteLine("Hello, World!"); - } - } -} diff --git a/__tests__/sample-csproj/sample.csproj b/__tests__/sample-csproj/sample.csproj deleted file mode 100644 index be6d7ea..0000000 --- a/__tests__/sample-csproj/sample.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - netcoreapp3.1;netcoreapp3.0;netcoreapp2.2 - sample_csproj - - false - - - - - - - - - - - diff --git a/__tests__/verify-dotnet.ps1 b/__tests__/verify-dotnet.ps1 index 7068c87..53befbb 100755 --- a/__tests__/verify-dotnet.ps1 +++ b/__tests__/verify-dotnet.ps1 @@ -1,73 +1,96 @@ -if (!$args[0]) -{ - throw "Must supply dotnet version argument" -} +param( + [ValidateNotNullOrEmpty()] + [string[]]$Patterns, + [ValidateNotNullOrEmpty()] + [string]$ContainedPattern, + [switch]$CheckNugetConfig +) -$dotnet = Get-Command dotnet | Select-Object -First 1 | ForEach-Object { $_.Path } -Write-Host "Found '$dotnet'" - -if($args.count -eq 1) -{ - $version = & $dotnet --version | Out-String | ForEach-Object { $_.Trim() } - Write-Host "Version $version" - if (-not ($version.StartsWith($args[0].ToString()))) +if ($CheckNugetConfig.IsPresent) { + if (!(Test-Path "../nuget.config")) { - Write-Host "PATH='$env:PATH'" - throw "Unexpected version" + throw "The nuget.config file is not generated correctly." } } -if ($args[1]) +if (!$Patterns.Count) { - # SDKs are listed on multiple lines with the path afterwards in square brackets - $versions = & $dotnet --list-sdks | ForEach-Object { $_.SubString(0, $_.IndexOf('[')).Trim() } - Write-Host "Installed versions: $versions" - $InstalledVersionCount = 0 - foreach($arg in $args){ - foreach ($version in $versions) + throw "At least 1 dotnet-version pattern should be supplied to script." +} + +Write-Host "Those patterns were supplied to the script: $($Patterns -join ', ')." +$dotnet = Get-Command dotnet | Select-Object -First 1 | ForEach-Object { $_.Path } +Write-Host "Found: '$dotnet'" + +# SDKs are listed on multiple lines with the path afterwards in square brackets +$versions = & $dotnet --list-sdks | ForEach-Object { $_.SubString(0, $_.IndexOf('[')).Trim() } +Write-Host "Installed versions: $($versions -join ', ')." +$InstalledVersionCount = 0 +foreach($pattern in $Patterns) +{ + foreach ($version in $versions) + { + if ($ContainedPattern) { - if ($version.StartsWith($arg.ToString())) + if ($version.StartsWith($pattern.ToString()) -and $version.Contains($ContainedPattern)) { $InstalledVersionCount++ + } + } + elseif ($version.StartsWith($pattern.ToString())) + { + $InstalledVersionCount++ } - } - } - if ( $InstalledVersionCount -ne $args.Count) - { - Write-Host "PATH='$env:PATH'" - throw "Unexpected version" } } +if ( $InstalledVersionCount -ne $Patterns.Count) +{ + throw "An unexpected version of Dotnet is found on the machine, please check the script's dotnet-version patterns." +} -Write-Host "Building sample csproj" -& $dotnet build __tests__/sample-csproj/ --no-cache -if ($LASTEXITCODE -ne 0) +Write-Host "Changing directory to the ./__tests__/e2e-test-csproj" +Set-Location ./__tests__/e2e-test-csproj + +$targetFrameworkVersionMapping = @{ + "1.0" = "netcoreapp1.0"; + "1.1" = "netcoreapp1.1"; + "2.0" = "netcoreapp2.0"; + "2.1" = "netcoreapp2.1"; + "2.2" = "netcoreapp2.2"; + "3.0" = "netcoreapp3.0"; + "3.1" = "netcoreapp3.1"; + "5.0" = "net5.0"; + "6.0" = "net6.0"; + "7.0" = "net7.0"; + } + +foreach ($version in $versions) { - throw "Unexpected exit code $LASTEXITCODE" + Write-Host "Creating temporary global.json file for $version .NET version." + & $dotnet new globaljson --sdk-version $version --force + Write-Host "The global.json file for the version $version is created. Currently used .NET version is: $(& $dotnet --version)" + $version -match "^(?\d+\.\d+)" + Write-Host "Setting the TEST_TARGET_FRAMEWORK environment variable to $($targetFrameworkVersionMapping[$Matches.key])" + [Environment]::SetEnvironmentVariable('TEST_TARGET_FRAMEWORK', $($targetFrameworkVersionMapping[$Matches.key])) + + Write-Host "Building test C# project with $version .NET version." + & $dotnet build --no-cache + if ($LASTEXITCODE -ne 0) + { + throw "Building process is not successful, exit code: $LASTEXITCODE" + } + + Write-Host "Testing compiled C# project with $version .NET version." + & $dotnet test --no-build + if ($LASTEXITCODE -ne 0) + { + throw "Testing process is not successful, exit code: $LASTEXITCODE" + } + + Write-Host "Tests are completed successfully!" + + Write-Host "Removing temprary global.json file." + Remove-Item ./global.json } -Write-Host "Testing compiled app" -$sample_output = "$(dotnet test __tests__/sample-csproj/ --no-build)" -Write-Host "Sample output: $sample_output" -# For Side-by-Side installs we want to run the tests twice, for a single install the tests will run once -if ($args[1]) -{ - if ($sample_output -notlike "*Test Run Successful.*Test Run Successful.*") - { - throw "Unexpected output" - } -} -if ($args[2]) -{ - if ($sample_output -notlike "*Test Run Successful.*Test Run Successful.*Test Run Successful.*") - { - throw "Unexpected output" - } -} -else -{ - if ($sample_output -notlike "*Test Run Successful.*") - { - throw "Unexpected output" - } -} +Set-Location ../.. \ No newline at end of file From 180a15970f461b60527b6ce1498e27bb3fcca3c6 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Wed, 19 Apr 2023 13:40:44 +0200 Subject: [PATCH 12/32] Update e2e test infrastructure --- .github/workflows/e2e-tests.yml | 48 ++++++++------ .github/workflows/test-dotnet.yml | 8 +-- .gitignore | 4 +- __tests__/e2e-test-csproj/Test.cs | 8 ++- __tests__/e2e-test-csproj/test.csproj | 2 +- __tests__/verify-dotnet.ps1 | 90 ++++++++++++++++----------- __tests__/verify-dotnet.sh | 44 ------------- 7 files changed, 92 insertions(+), 112 deletions(-) delete mode 100755 __tests__/verify-dotnet.sh diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index fd6e85b..9963bbc 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -33,7 +33,7 @@ jobs: 3.0.x - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 -Patterns 2.2.402, 3.1.404, 3.0 + run: __tests__/verify-dotnet.ps1 -Patterns "^2.2.402$", "^3.1.404$", "^3.0" test-setup-full-version: runs-on: ${{ matrix.operating-system }} @@ -62,7 +62,7 @@ jobs: NUGET_AUTH_TOKEN: NOTATOKEN - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 -Patterns 3.1.201, 2.2.402 -CheckNugetConfig + run: __tests__/verify-dotnet.ps1 -Patterns "^3.1.201$", "^2.2.402$" -CheckNugetConfig test-setup-without-patch-version: runs-on: ${{ matrix.operating-system }} @@ -87,7 +87,7 @@ jobs: dotnet-version: '2.2' - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 -Patterns 3.1, 2.2 + run: __tests__/verify-dotnet.ps1 -Patterns "^3.1", "^2.2" test-setup-prerelease-version: runs-on: ${{ matrix.operating-system }} @@ -107,7 +107,7 @@ jobs: dotnet-version: '3.1.100-preview1-014459' - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 -Patterns 3.1.100-preview1-014459 + run: __tests__/verify-dotnet.ps1 -Patterns "3.1.100-preview1-014459" test-setup-latest-patch-version: runs-on: ${{ matrix.operating-system }} @@ -131,7 +131,7 @@ jobs: dotnet-version: 2.2.X - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 -Patterns 2.2, 3.1 + run: __tests__/verify-dotnet.ps1 -Patterns "^2.2", "^3.1" test-setup-with-wildcard: runs-on: ${{ matrix.operating-system }} @@ -155,7 +155,7 @@ jobs: dotnet-version: 2.2.* - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 -Patterns 3.1, 2.2 + run: __tests__/verify-dotnet.ps1 -Patterns "^3.1", "^2.2" test-setup-global-json-specified-and-version: runs-on: ${{ matrix.operating-system }} @@ -181,7 +181,7 @@ jobs: global-json-file: ./subdirectory/global.json - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 -Patterns 2.2.207, 3.1 + run: __tests__/verify-dotnet.ps1 -Patterns "^2.2", "^3.1" test-setup-with-dotnet-quality: runs-on: ${{ matrix.operating-system }} @@ -203,7 +203,7 @@ jobs: dotnet-quality: 'preview' - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 -Patterns 7.0 -ContainedPattern "preview" + run: __tests__/verify-dotnet.ps1 -Patterns "^7\.0\.\d+-" test-dotnet-version-output-during-single-version-installation: runs-on: ${{ matrix.operating-system }} @@ -262,7 +262,7 @@ jobs: test-proxy: runs-on: ubuntu-latest container: - image: mcr.microsoft.com/dotnet/core/runtime-deps:3.0-bionic + image: ubuntu:latest options: --dns 127.0.0.1 services: squid-proxy: @@ -275,22 +275,29 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 - - name: Clear tool cache - run: rm -rf "/usr/share/dotnet" - - name: Install curl + - name: Install Powershell run: | - apt update - apt -y install curl - - name: Setup dotnet 3.1.201 + apt-get update + apt-get install -y wget apt-transport-https software-properties-common + wget -q "https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb" + dpkg -i packages-microsoft-prod.deb + rm packages-microsoft-prod.deb + apt-get update + apt-get install -y powershell + - name: Clear toolcache + shell: pwsh + run: __tests__/clear-toolcache.ps1 ${{ runner.os }} + - name: Setup dotnet 6.0 uses: ./ with: - dotnet-version: 3.1.201 + dotnet-version: 6.0 source-url: https://api.nuget.org/v3/index.json env: NUGET_AUTH_TOKEN: NOTATOKEN - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 -Patterns 3.1.201 -CheckNugetConfig + run: | + __tests__/verify-dotnet.ps1 -Patterns "^6.0" -CheckNugetConfig test-bypass-proxy: runs-on: ubuntu-latest @@ -300,8 +307,9 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 - - name: Clear tool cache - run: rm -rf "/usr/share/dotnet" + - name: Clear toolcache + shell: pwsh + run: __tests__/clear-toolcache.ps1 ${{ runner.os }} - name: Setup dotnet 3.1.201 uses: ./ with: @@ -311,4 +319,4 @@ jobs: NUGET_AUTH_TOKEN: NOTATOKEN - name: Verify dotnet shell: pwsh - run: __tests__/verify-dotnet.ps1 -Patterns 3.1.201 -CheckNugetConfig + run: __tests__/verify-dotnet.ps1 -Patterns "^3.1.201$" -CheckNugetConfig diff --git a/.github/workflows/test-dotnet.yml b/.github/workflows/test-dotnet.yml index 0b37d28..760167b 100644 --- a/.github/workflows/test-dotnet.yml +++ b/.github/workflows/test-dotnet.yml @@ -18,7 +18,7 @@ jobs: fail-fast: false matrix: operating-system: [ubuntu-latest, windows-latest, macOS-latest] - dotnet-version: ['2.1', '2.2', '3.0', '3.1', '5.0'] + dotnet-version: ['2.1', '2.2', '3.0', '3.1', '5.0', '6.0', '7.0'] steps: - name: Checkout uses: actions/checkout@v3 @@ -29,9 +29,7 @@ jobs: uses: ./ with: dotnet-version: ${{ matrix.dotnet-version }} - - name: Check installed version + - name: Verify installed version shell: pwsh run: | - $version = & dotnet --version - Write-Host "Installed version: $version" - if (-not $version.StartsWith("${{ matrix.dotnet-version }}")) { throw "Unexpected version" } + __tests__/verify-dotnet.ps1 -Patterns "^${{ matrix.dotnet-version }}" diff --git a/.gitignore b/.gitignore index b339e2c..1cb082a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,8 @@ global.json lib/ node_modules/ __tests__/runner/* -__tests__/sample-csproj/bin/ -__tests__/sample-csproj/obj/ +__tests__/e2e-test-csproj/bin/ +__tests__/e2e-test-csproj/obj/ # Rest of the file pulled from https://github.com/github/gitignore/blob/master/Node.gitignore # Logs diff --git a/__tests__/e2e-test-csproj/Test.cs b/__tests__/e2e-test-csproj/Test.cs index 533bdb8..ba9573a 100644 --- a/__tests__/e2e-test-csproj/Test.cs +++ b/__tests__/e2e-test-csproj/Test.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; namespace test_csproj { @@ -7,8 +8,11 @@ namespace test_csproj { [TestMethod] public void TestMethod() - { - Assert.AreEqual((1 + 1), 2); + { + Console.WriteLine("TestMethod"); + int calculatedResult = 1000 / 25; + int expectedResult = 40; + Assert.AreEqual(calculatedResult, expectedResult); } } } diff --git a/__tests__/e2e-test-csproj/test.csproj b/__tests__/e2e-test-csproj/test.csproj index 370186a..769269f 100644 --- a/__tests__/e2e-test-csproj/test.csproj +++ b/__tests__/e2e-test-csproj/test.csproj @@ -2,11 +2,11 @@ $(TEST_TARGET_FRAMEWORK) - false + diff --git a/__tests__/verify-dotnet.ps1 b/__tests__/verify-dotnet.ps1 index 53befbb..496fec2 100755 --- a/__tests__/verify-dotnet.ps1 +++ b/__tests__/verify-dotnet.ps1 @@ -1,57 +1,68 @@ +<# + .DESCRIPTION + Verifies that installed on the machine .NET SDK versions match the input patterns. + Optionally checks that the nuget.config file is generated correctly. + + .PARAMETER Patterns + Specifies the regular expression patterns that should be matched with the installed + on the machine .NET SDK versions. The number of patterns should be equal to the number + of installed .NET versions. + + .PARAMETER CheckNugetConfig + Switches the check for the existence of the nuget.config file. + + .EXAMPLE + PS> .\verify-dotnet.ps1 -Paterns "^3.1.200$", "^6.0" -CheckNugetConfig +#> + param( [ValidateNotNullOrEmpty()] + [Parameter(Mandatory=$true)] [string[]]$Patterns, - [ValidateNotNullOrEmpty()] - [string]$ContainedPattern, [switch]$CheckNugetConfig ) -if ($CheckNugetConfig.IsPresent) { - if (!(Test-Path "../nuget.config")) - { - throw "The nuget.config file is not generated correctly." - } +$PatternsList = [System.Collections.ArrayList]($Patterns) + +if ($CheckNugetConfig.IsPresent -and !(Test-Path "../nuget.config")) { + throw "The nuget.config file is not generated correctly." } -if (!$Patterns.Count) -{ - throw "At least 1 dotnet-version pattern should be supplied to script." -} +$PatternsCount = $PatternsList.Count -Write-Host "Those patterns were supplied to the script: $($Patterns -join ', ')." +Write-Host "Those patterns were supplied to the script: $($PatternsList -join ', ')." $dotnet = Get-Command dotnet | Select-Object -First 1 | ForEach-Object { $_.Path } Write-Host "Found: '$dotnet'" # SDKs are listed on multiple lines with the path afterwards in square brackets -$versions = & $dotnet --list-sdks | ForEach-Object { $_.SubString(0, $_.IndexOf('[')).Trim() } -Write-Host "Installed versions: $($versions -join ', ')." -$InstalledVersionCount = 0 -foreach($pattern in $Patterns) +$Versions = & $dotnet --list-sdks | ForEach-Object { $_.SubString(0, $_.IndexOf('[')).Trim() } +Write-Host "Found installed versions: $($Versions -join ', ')." +$InstalledVersionCount = $Versions.Count + +foreach($version in $Versions) { - foreach ($version in $versions) + foreach($pattern in $PatternsList) { - if ($ContainedPattern) - { - if ($version.StartsWith($pattern.ToString()) -and $version.Contains($ContainedPattern)) - { - $InstalledVersionCount++ - } - } - elseif ($version.StartsWith($pattern.ToString())) - { - $InstalledVersionCount++ - } + if ($version -match $pattern) + { + $PatternsList.Remove($pattern) + $InstalledVersionCount-- + break + } } } -if ( $InstalledVersionCount -ne $Patterns.Count) + +if ( $InstalledVersionCount -ne 0) { - throw "An unexpected version of Dotnet is found on the machine, please check the script's dotnet-version patterns." + throw "An unexpected version of Dotnet is found on the machine, please check the correctness of the -Patterns input." } Write-Host "Changing directory to the ./__tests__/e2e-test-csproj" -Set-Location ./__tests__/e2e-test-csproj +$workingDir = Get-Location +$testProjectDir = "./__tests__/e2e-test-csproj" +Set-Location $testProjectDir -$targetFrameworkVersionMapping = @{ +$targetFrameworkVersionMap = @{ "1.0" = "netcoreapp1.0"; "1.1" = "netcoreapp1.1"; "2.0" = "netcoreapp2.0"; @@ -64,14 +75,17 @@ $targetFrameworkVersionMapping = @{ "7.0" = "net7.0"; } -foreach ($version in $versions) +foreach ($version in $Versions) { + # Creating temporary global.json file inside e2e-test-csproj dir and setting exact version of .NET inside allows to override default behavior of .NET and run build and tests on that exact version. Write-Host "Creating temporary global.json file for $version .NET version." & $dotnet new globaljson --sdk-version $version --force - Write-Host "The global.json file for the version $version is created. Currently used .NET version is: $(& $dotnet --version)" + Write-Host "The global.json file for the version $version is created. Currently used .NET version is: $(& $dotnet --version)." + + # Environment variable TEST_TARGET_FRAMEWORK is used inside the test.csproj file to target required framework version $version -match "^(?\d+\.\d+)" - Write-Host "Setting the TEST_TARGET_FRAMEWORK environment variable to $($targetFrameworkVersionMapping[$Matches.key])" - [Environment]::SetEnvironmentVariable('TEST_TARGET_FRAMEWORK', $($targetFrameworkVersionMapping[$Matches.key])) + Write-Host "Setting the TEST_TARGET_FRAMEWORK environment variable to $($targetFrameworkVersionMap[$Matches.key])" + [Environment]::SetEnvironmentVariable('TEST_TARGET_FRAMEWORK', $($targetFrameworkVersionMap[$Matches.key])) Write-Host "Building test C# project with $version .NET version." & $dotnet build --no-cache @@ -89,8 +103,8 @@ foreach ($version in $versions) Write-Host "Tests are completed successfully!" - Write-Host "Removing temprary global.json file." + Write-Host "Removing temporary global.json file." Remove-Item ./global.json } -Set-Location ../.. \ No newline at end of file +Set-Location $workingDir \ No newline at end of file diff --git a/__tests__/verify-dotnet.sh b/__tests__/verify-dotnet.sh deleted file mode 100755 index 098d076..0000000 --- a/__tests__/verify-dotnet.sh +++ /dev/null @@ -1,44 +0,0 @@ -if [ -z "$1" ]; then - echo "Must supply dotnet version argument" - exit 1 -fi - -if [ ! -f "../nuget.config" ]; then - echo "nuget file not generated correctly" - exit 1 -fi - -dotnet_version="$(dotnet --version)" -echo "Found dotnet version '$dotnet_version'" -if [ -z "$(echo $dotnet_version | grep $1)" ]; then - echo "Unexpected version" - exit 1 -fi - -if [ -n "$2" ]; then - dotnet_version="$(dotnet --list-sdks)" - echo "Found dotnet version '$dotnet_version'" - if [ -z "$(echo $dotnet_version | grep $2)" ]; then - echo "Unexpected version" - exit 1 - fi -fi - -echo "Building sample csproj" -dotnet build __tests__/sample-csproj/ --no-cache || exit 1 - -echo "Testing compiled app" -sample_output=$(dotnet test __tests__/sample-csproj/ --no-build) -echo "Sample output: $sample_output" -# For Side-by-Side installs we want to run the tests twice, for a single install the tests will run once -if [ -n "$2" ]; then - if [ -z "$(echo $sample_output | grep "Test Run Successful.*Test Run Successful.")" ]; then - echo "Unexpected output" - exit 1 - fi -else - if [ -z "$(echo $sample_output | grep "Test Run Successful.")" ]; then - echo "Unexpected output" - exit 1 - fi -fi \ No newline at end of file From a79ce57e6b19c164cea1267185d12ae20544407c Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Wed, 19 Apr 2023 13:43:18 +0200 Subject: [PATCH 13/32] Update contribution documentation --- docs/contributors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributors.md b/docs/contributors.md index 0141e97..3d2a55d 100644 --- a/docs/contributors.md +++ b/docs/contributors.md @@ -67,7 +67,7 @@ Pull requests are the easiest way to contribute changes to git repos at GitHub. **Learn more about how to implement tests:** Adding or changing tests is an integral part of making a change to the code. -Unit tests are in the `__tests__` folder, and end-to-end tests are in the `workflows` folder, particularly in the [workflow.yml](https://github.com/actions/setup-dotnet/blob/main/.github/workflows/workflow.yml). +Unit tests are in the `__tests__` folder, and end-to-end tests are in the `workflows` folder, particularly in the [e2e-tests.yml](https://github.com/actions/setup-dotnet/blob/main/.github/workflows/e2e-tests.yml). - The contributor can add various types of tests (like unit tests or end-to-end tests), which, in his opinion, will be necessary and sufficient for testing new or changed functionality - Tests should cover a successful execution, as well as some edge cases and possible errors From e8ac21d503508fd057b818619cd0e31c3db74e30 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Wed, 19 Apr 2023 15:41:28 +0200 Subject: [PATCH 14/32] Fix typos --- __tests__/verify-dotnet.ps1 | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/__tests__/verify-dotnet.ps1 b/__tests__/verify-dotnet.ps1 index 496fec2..b937a14 100755 --- a/__tests__/verify-dotnet.ps1 +++ b/__tests__/verify-dotnet.ps1 @@ -28,9 +28,7 @@ if ($CheckNugetConfig.IsPresent -and !(Test-Path "../nuget.config")) { throw "The nuget.config file is not generated correctly." } -$PatternsCount = $PatternsList.Count - -Write-Host "Those patterns were supplied to the script: $($PatternsList -join ', ')." +Write-Host "These patterns were supplied to the script: $($PatternsList -join ', ')." $dotnet = Get-Command dotnet | Select-Object -First 1 | ForEach-Object { $_.Path } Write-Host "Found: '$dotnet'" @@ -57,9 +55,9 @@ if ( $InstalledVersionCount -ne 0) throw "An unexpected version of Dotnet is found on the machine, please check the correctness of the -Patterns input." } -Write-Host "Changing directory to the ./__tests__/e2e-test-csproj" $workingDir = Get-Location $testProjectDir = "./__tests__/e2e-test-csproj" +Write-Host "Changing directory to the $testProjectDir" Set-Location $testProjectDir $targetFrameworkVersionMap = @{ From 50b46b3b1d8734a2d964e6961fe5ba5ee1b91280 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Wed, 19 Apr 2023 16:24:27 +0200 Subject: [PATCH 15/32] Update verify-dotnet.ps1 --- .github/workflows/test-dotnet.yml | 2 +- __tests__/verify-dotnet.ps1 | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-dotnet.yml b/.github/workflows/test-dotnet.yml index 760167b..03b9a29 100644 --- a/.github/workflows/test-dotnet.yml +++ b/.github/workflows/test-dotnet.yml @@ -18,7 +18,7 @@ jobs: fail-fast: false matrix: operating-system: [ubuntu-latest, windows-latest, macOS-latest] - dotnet-version: ['2.1', '2.2', '3.0', '3.1', '5.0', '6.0', '7.0'] + dotnet-version: ['2.1', '2.2', '3.0', '3.1', '5.0', '6.0', '7.0', '8.0'] steps: - name: Checkout uses: actions/checkout@v3 diff --git a/__tests__/verify-dotnet.ps1 b/__tests__/verify-dotnet.ps1 index b937a14..fa4876d 100755 --- a/__tests__/verify-dotnet.ps1 +++ b/__tests__/verify-dotnet.ps1 @@ -71,6 +71,7 @@ $targetFrameworkVersionMap = @{ "5.0" = "net5.0"; "6.0" = "net6.0"; "7.0" = "net7.0"; + "8.0" = "net8.0"; } foreach ($version in $Versions) @@ -78,10 +79,18 @@ foreach ($version in $Versions) # Creating temporary global.json file inside e2e-test-csproj dir and setting exact version of .NET inside allows to override default behavior of .NET and run build and tests on that exact version. Write-Host "Creating temporary global.json file for $version .NET version." & $dotnet new globaljson --sdk-version $version --force + if (!(Test-Path "./global.json")) + { + throw "An error occured while creating the global.json file. Exit code: $LASTEXITCODE" + } Write-Host "The global.json file for the version $version is created. Currently used .NET version is: $(& $dotnet --version)." # Environment variable TEST_TARGET_FRAMEWORK is used inside the test.csproj file to target required framework version - $version -match "^(?\d+\.\d+)" + $version -match "^(?\d+\.\d+)" | Out-Null + if (!($targetFrameworkVersionMap.ContainsKey($Matches.key))) + { + throw "The map with the framework targets doesn't contain a target name for the version $version." + } Write-Host "Setting the TEST_TARGET_FRAMEWORK environment variable to $($targetFrameworkVersionMap[$Matches.key])" [Environment]::SetEnvironmentVariable('TEST_TARGET_FRAMEWORK', $($targetFrameworkVersionMap[$Matches.key])) From 255362be61a1c67b8f9b3441049f77c0aa1e8898 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Wed, 19 Apr 2023 16:32:38 +0200 Subject: [PATCH 16/32] Silent dotnet new globaljson command --- __tests__/verify-dotnet.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/verify-dotnet.ps1 b/__tests__/verify-dotnet.ps1 index fa4876d..187c715 100755 --- a/__tests__/verify-dotnet.ps1 +++ b/__tests__/verify-dotnet.ps1 @@ -78,7 +78,7 @@ foreach ($version in $Versions) { # Creating temporary global.json file inside e2e-test-csproj dir and setting exact version of .NET inside allows to override default behavior of .NET and run build and tests on that exact version. Write-Host "Creating temporary global.json file for $version .NET version." - & $dotnet new globaljson --sdk-version $version --force + & $dotnet new globaljson --sdk-version $version --force | Out-Null if (!(Test-Path "./global.json")) { throw "An error occured while creating the global.json file. Exit code: $LASTEXITCODE" From 7d08dc7593e68a8fd87b871930df2cace89d7c66 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Wed, 19 Apr 2023 16:48:00 +0200 Subject: [PATCH 17/32] Add e2e test --- .github/workflows/e2e-tests.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 9963bbc..31ecca7 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -205,6 +205,27 @@ jobs: shell: pwsh run: __tests__/verify-dotnet.ps1 -Patterns "^7\.0\.\d+-" + test-ABCxx-syntax: + runs-on: ${{ matrix.operating-system }} + strategy: + fail-fast: false + matrix: + operating-system: [ubuntu-latest, windows-latest, macOS-latest] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Clear toolcache + shell: pwsh + run: __tests__/clear-toolcache.ps1 ${{ runner.os }} + + - name: Setup dotnet 6.0.4xx + uses: ./ + with: + dotnet-version: '6.0.4xx' + - name: Verify dotnet + shell: pwsh + run: __tests__/verify-dotnet.ps1 -Patterns "^6\.0\.4\d{2}" + test-dotnet-version-output-during-single-version-installation: runs-on: ${{ matrix.operating-system }} strategy: From 6adeb768cee8b4e95f5d006c4d702894fe7eb050 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Fri, 5 May 2023 10:43:09 +0200 Subject: [PATCH 18/32] Update docs --- README.md | 3 +- __tests__/installer.test.ts | 368 +++++++++++++++++------------------- action.yml | 2 +- dist/index.js | 21 +- src/installer.ts | 23 +-- 5 files changed, 197 insertions(+), 220 deletions(-) diff --git a/README.md b/README.md index b275fc3..c2f6d55 100644 --- a/README.md +++ b/README.md @@ -49,12 +49,13 @@ The `dotnet-version` input supports following syntax: - **A.B.C** (e.g 6.0.400, 7.0.100-preview.7.22377.5) - installs exact version of .NET SDK - **A.B** or **A.B.x** (e.g. 3.1, 3.1.x) - installs the latest patch version of .NET SDK on the channel `3.1`, including prerelease versions (preview, rc) - **A** or **A.x** (e.g. 3, 3.x) - installs the latest minor version of the specified major tag, including prerelease versions (preview, rc) +- **A.B.Cxx** (e.g. 6.0.4xx) - available since `.NET 5.0` release. Installs the latest version of the specific SDK release, including prerelease versions (preview, rc). ## Using the `dotnet-quality` input This input sets up the action to install the latest build of the specified quality in the channel. The possible values of `dotnet-quality` are: **daily**, **signed**, **validated**, **preview**, **ga**. -> **Note**: `dotnet-quality` input can be used only with .NET SDK version in 'A.B', 'A.B.x', 'A' and 'A.x' formats where the major version is higher than 5. In other cases, `dotnet-quality` input will be ignored. +> **Note**: `dotnet-quality` input can be used only with .NET SDK version in 'A.B', 'A.B.x', 'A', 'A.x' and 'A.B.Cxx' formats where the major version is higher than 5. In other cases, `dotnet-quality` input will be ignored. ```yml steps: diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 363310c..4830e7a 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -53,232 +53,202 @@ describe('DotnetCoreInstaller tests', () => { } }, 30000); - it('Aquires multiple versions of dotnet', async () => { - const versions = ['2.2.207', '3.1.120']; + // it('Aquires multiple versions of dotnet', async () => { + // const versions = ['2.2.207', '3.1.120']; - for (const version of versions) { - await getDotnet(version); - } - expect(fs.existsSync(path.join(toolDir, 'sdk', '2.2.207'))).toBe(true); - expect(fs.existsSync(path.join(toolDir, 'sdk', '3.1.120'))).toBe(true); + // for (const version of versions) { + // await getDotnet(version); + // } + // expect(fs.existsSync(path.join(toolDir, 'sdk', '2.2.207'))).toBe(true); + // expect(fs.existsSync(path.join(toolDir, 'sdk', '3.1.120'))).toBe(true); - if (IS_WINDOWS) { - expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); - } else { - expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); - } + // if (IS_WINDOWS) { + // expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); + // } else { + // expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); + // } - expect(process.env.DOTNET_ROOT).toBeDefined(); - expect(process.env.PATH).toBeDefined(); - expect(process.env.DOTNET_ROOT).toBe(toolDir); - expect(process.env.PATH?.startsWith(toolDir)).toBe(true); - }, 600000); + // expect(process.env.DOTNET_ROOT).toBeDefined(); + // expect(process.env.PATH).toBeDefined(); + // expect(process.env.DOTNET_ROOT).toBe(toolDir); + // expect(process.env.PATH?.startsWith(toolDir)).toBe(true); + // }, 600000); - it('Acquires version of dotnet if no matching version is installed', async () => { - await getDotnet('3.1.201'); - expect(fs.existsSync(path.join(toolDir, 'sdk', '3.1.201'))).toBe(true); - if (IS_WINDOWS) { - expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); - } else { - expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); - } + // it('Acquires version of dotnet if no matching version is installed', async () => { + // await getDotnet('3.1.201'); + // expect(fs.existsSync(path.join(toolDir, 'sdk', '3.1.201'))).toBe(true); + // if (IS_WINDOWS) { + // expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); + // } else { + // expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); + // } - expect(process.env.DOTNET_ROOT).toBeDefined(); - expect(process.env.PATH).toBeDefined(); - expect(process.env.DOTNET_ROOT).toBe(toolDir); - expect(process.env.PATH?.startsWith(toolDir)).toBe(true); - }, 600000); //This needs some time to download on "slower" internet connections + // expect(process.env.DOTNET_ROOT).toBeDefined(); + // expect(process.env.PATH).toBeDefined(); + // expect(process.env.DOTNET_ROOT).toBe(toolDir); + // expect(process.env.PATH?.startsWith(toolDir)).toBe(true); + // }, 600000); //This needs some time to download on "slower" internet connections - it('Acquires generic version of dotnet if no matching version is installed', async () => { - await getDotnet('3.1'); - const directory = fs - .readdirSync(path.join(toolDir, 'sdk')) - .filter(fn => fn.startsWith('3.1.')); - expect(directory.length > 0).toBe(true); - if (IS_WINDOWS) { - expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); - } else { - expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); - } + // it('Acquires generic version of dotnet if no matching version is installed', async () => { + // await getDotnet('3.1'); + // const directory = fs + // .readdirSync(path.join(toolDir, 'sdk')) + // .filter(fn => fn.startsWith('3.1.')); + // expect(directory.length > 0).toBe(true); + // if (IS_WINDOWS) { + // expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); + // } else { + // expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); + // } - expect(process.env.DOTNET_ROOT).toBeDefined(); - expect(process.env.PATH).toBeDefined(); - expect(process.env.DOTNET_ROOT).toBe(toolDir); - expect(process.env.PATH?.startsWith(toolDir)).toBe(true); - }, 600000); //This needs some time to download on "slower" internet connections + // expect(process.env.DOTNET_ROOT).toBeDefined(); + // expect(process.env.PATH).toBeDefined(); + // expect(process.env.DOTNET_ROOT).toBe(toolDir); + // expect(process.env.PATH?.startsWith(toolDir)).toBe(true); + // }, 600000); //This needs some time to download on "slower" internet connections it('Returns string with installed SDK version', async () => { - const version = '3.1.120'; + const version = '6.0.1xx'; const installedVersion = await getDotnet(version); expect(installedVersion).toBe('3.1.120'); }, 600000); - - it('Throws if no location contains correct dotnet version', async () => { - await expect(async () => { - await getDotnet('1000.0.0'); - }).rejects.toThrow(); - }, 30000); - - it('Uses an up to date bash download script', async () => { - const httpCallbackClient = new hc.HttpClient('setup-dotnet-test', [], { - allowRetries: true, - maxRetries: 3 - }); - const response: hc.HttpClientResponse = await httpCallbackClient.get( - 'https://dot.net/v1/dotnet-install.sh' - ); - expect(response.message.statusCode).toBe(200); - const upToDateContents: string = await response.readBody(); - const currentContents: string = fs - .readFileSync( - path.join(__dirname, '..', 'externals', 'install-dotnet.sh') - ) - .toString(); - expect(normalizeFileContents(currentContents)).toBe( - normalizeFileContents(upToDateContents) - ); - }, 30000); - - it('Uses an up to date powershell download script', async () => { - const httpCallbackClient = new hc.HttpClient('setup-dotnet-test', [], { - allowRetries: true, - maxRetries: 3 - }); - const response: hc.HttpClientResponse = await httpCallbackClient.get( - 'https://dot.net/v1/dotnet-install.ps1' - ); - expect(response.message.statusCode).toBe(200); - const upToDateContents: string = await response.readBody(); - const currentContents: string = fs - .readFileSync( - path.join(__dirname, '..', 'externals', 'install-dotnet.ps1') - ) - .toString(); - expect(normalizeFileContents(currentContents)).toBe( - normalizeFileContents(upToDateContents) - ); - }, 30000); }); -describe('DotnetVersionResolver tests', () => { - each([ - '3.1', - '3.x', - '3.1.x', - '3.1.*', - '3.1.X', - '3.1.2', - '3.1.0-preview1' - ]).test( - "if valid version: '%s' is supplied, it should return version object with some value", - async version => { - const dotnetVersionResolver = new installer.DotnetVersionResolver( - version - ); - const versionObject = await dotnetVersionResolver.createDotNetVersion(); +// describe('DotnetVersionResolver tests', () => { +// each([ +// '3.1', +// '3.x', +// '3.1.x', +// '3.1.*', +// '3.1.X', +// '3.1.2', +// '3.1.0-preview1' +// ]).test( +// "if valid version: '%s' is supplied, it should return version object with some value", +// async version => { +// const dotnetVersionResolver = new installer.DotnetVersionResolver( +// version +// ); +// const versionObject = await dotnetVersionResolver.createDotNetVersion(); - expect(!!versionObject.value).toBe(true); - } - ); +// expect(!!versionObject.value).toBe(true); +// } +// ); - each([ - '.', - '..', - ' . ', - '. ', - ' .', - ' . . ', - ' .. ', - ' . ', - '-1.-1', - '-1', - '-1.-1.-1', - '..3', - '1..3', - '1..', - '.2.3', - '.2.x', - '*.', - '1.2.', - '1.2.-abc', - 'a.b', - 'a.b.c', - 'a.b.c-preview', - ' 0 . 1 . 2 ', - 'invalid' - ]).test( - "if invalid version: '%s' is supplied, it should throw", - async version => { - const dotnetVersionResolver = new installer.DotnetVersionResolver( - version - ); +// each([ +// '.', +// '..', +// ' . ', +// '. ', +// ' .', +// ' . . ', +// ' .. ', +// ' . ', +// '-1.-1', +// '-1', +// '-1.-1.-1', +// '..3', +// '1..3', +// '1..', +// '.2.3', +// '.2.x', +// '*.', +// '1.2.', +// '1.2.-abc', +// 'a.b', +// 'a.b.c', +// 'a.b.c-preview', +// ' 0 . 1 . 2 ', +// 'invalid' +// ]).test( +// "if invalid version: '%s' is supplied, it should throw", +// async version => { +// const dotnetVersionResolver = new installer.DotnetVersionResolver( +// version +// ); - await expect( - async () => await dotnetVersionResolver.createDotNetVersion() - ).rejects.toThrow(); - } - ); +// await expect( +// async () => await dotnetVersionResolver.createDotNetVersion() +// ).rejects.toThrow(); +// } +// ); - each(['3.1', '3.1.x', '3.1.*', '3.1.X', '5.0.1xx']).test( - "if version: '%s' that can be resolved to 'channel' option is supplied, it should set type to 'channel' in version object", - async version => { - const dotnetVersionResolver = new installer.DotnetVersionResolver( - version - ); - const versionObject = await dotnetVersionResolver.createDotNetVersion(); +// each(['3.1', '3.1.x', '3.1.*', '3.1.X', '5.0.1xx']).test( +// "if version: '%s' that can be resolved to 'channel' option is supplied, it should set type to 'channel' in version object", +// async version => { +// const dotnetVersionResolver = new installer.DotnetVersionResolver( +// version +// ); +// const versionObject = await dotnetVersionResolver.createDotNetVersion(); - expect(versionObject.type.toLowerCase().includes('channel')).toBe(true); - } - ); +// expect(versionObject.type.toLowerCase().includes('channel')).toBe(true); +// } +// ); - each(['6.0', '6.0.x', '6.0.*', '6.0.X', '6.0.1xx']).test( - "if version: '%s' that can be resolved to 'channel' option is supplied and its major tag is >= 6, it should set type to 'channel' and qualityFlag to 'true' in version object", - async version => { - const dotnetVersionResolver = new installer.DotnetVersionResolver( - version - ); - const versionObject = await dotnetVersionResolver.createDotNetVersion(); +// each(['6.0', '6.0.x', '6.0.*', '6.0.X', '6.0.1xx']).test( +// "if version: '%s' that can be resolved to 'channel' option is supplied and its major tag is >= 6, it should set type to 'channel' and qualityFlag to 'true' in version object", +// async version => { +// const dotnetVersionResolver = new installer.DotnetVersionResolver( +// version +// ); +// const versionObject = await dotnetVersionResolver.createDotNetVersion(); - expect(versionObject.type.toLowerCase().includes('channel')).toBe(true); - expect(versionObject.qualityFlag).toBe(true); - } - ); +// expect(versionObject.type.toLowerCase().includes('channel')).toBe(true); +// expect(versionObject.qualityFlag).toBe(true); +// } +// ); - each(['3.1.2', '3.1.0-preview1']).test( - "if version: '%s' that can be resolved to 'version' option is supplied, it should set quality flag to 'false' and type to 'version' in version object", - async version => { - const dotnetVersionResolver = new installer.DotnetVersionResolver( - version - ); - const versionObject = await dotnetVersionResolver.createDotNetVersion(); +// each(['3.1.2', '3.1.0-preview1']).test( +// "if version: '%s' that can be resolved to 'version' option is supplied, it should set quality flag to 'false' and type to 'version' in version object", +// async version => { +// const dotnetVersionResolver = new installer.DotnetVersionResolver( +// version +// ); +// const versionObject = await dotnetVersionResolver.createDotNetVersion(); - expect(versionObject.type.toLowerCase().includes('version')).toBe(true); - expect(versionObject.qualityFlag).toBe(false); - } - ); +// expect(versionObject.type.toLowerCase().includes('version')).toBe(true); +// expect(versionObject.qualityFlag).toBe(false); +// } +// ); - each(['3.1.2', '3.1']).test( - 'it should create proper line arguments for powershell/bash installation scripts', - async version => { - const dotnetVersionResolver = new installer.DotnetVersionResolver( - version - ); - const versionObject = await dotnetVersionResolver.createDotNetVersion(); - const windowsRegEx = new RegExp(/^-[VC]/); - const nonWindowsRegEx = new RegExp(/^--[vc]/); +// each(['3.1.2', '3.1']).test( +// 'it should create proper line arguments for powershell/bash installation scripts', +// async version => { +// const dotnetVersionResolver = new installer.DotnetVersionResolver( +// version +// ); +// const versionObject = await dotnetVersionResolver.createDotNetVersion(); +// const windowsRegEx = new RegExp(/^-[VC]/); +// const nonWindowsRegEx = new RegExp(/^--[vc]/); - if (IS_WINDOWS) { - expect(windowsRegEx.test(versionObject.type)).toBe(true); - expect(nonWindowsRegEx.test(versionObject.type)).toBe(false); - } else { - expect(nonWindowsRegEx.test(versionObject.type)).toBe(true); - expect(windowsRegEx.test(versionObject.type)).toBe(false); - } - } - ); -}); +// if (IS_WINDOWS) { +// expect(windowsRegEx.test(versionObject.type)).toBe(true); +// expect(nonWindowsRegEx.test(versionObject.type)).toBe(false); +// } else { +// expect(nonWindowsRegEx.test(versionObject.type)).toBe(true); +// expect(windowsRegEx.test(versionObject.type)).toBe(false); +// } +// } +// ); + +// it('Should throw if supplied dotnet version is in A.B.Cxx syntax and the major tag is lower than 5', async () => { +// const version = '3.1.1xx'; +// const dotnetVersionResolver = new installer.DotnetVersionResolver(version); +// await expect(dotnetVersionResolver.createDotNetVersion()).rejects.toThrow( +// `'dotnet-version' was supplied in invalid format: ${version}! The A.B.Cxx syntax is available since the .NET 5.0 release.` +// ); +// }, 600000); + +// it('Should resolve version supplied as * to channel type and set value to LTS', async () => { +// const version = '*'; +// const dotnetVersionResolver = new installer.DotnetVersionResolver(version); +// const versionObject = await dotnetVersionResolver.createDotNetVersion(); +// expect(versionObject.type.toLowerCase().includes('channel')).toBe(true); +// expect(versionObject.value).toBe('LTS'); +// }, 600000); +// }); function normalizeFileContents(contents: string): string { return contents diff --git a/action.yml b/action.yml index dafec86..eec3975 100644 --- a/action.yml +++ b/action.yml @@ -6,7 +6,7 @@ branding: color: green inputs: dotnet-version: - description: 'Optional SDK version(s) to use. If not provided, will install global.json version when available. Examples: 2.2.104, 3.1, 3.1.x, 3.x' + description: 'Optional SDK version(s) to use. If not provided, will install global.json version when available. Examples: 2.2.104, 3.1, 3.1.x, 3.x, 3.1.4xx' dotnet-quality: description: 'Optional quality of the build. The possible values are: daily, signed, validated, preview, ga.' global-json-file: diff --git a/dist/index.js b/dist/index.js index 40338d4..7346d82 100644 --- a/dist/index.js +++ b/dist/index.js @@ -297,7 +297,7 @@ class DotnetVersionResolver { this.resolvedArgument.value = yield this.getLatestByMajorTag(major); } else { - // If "dotnet-version" is specified as *, x or X resolve latest version of .NET explicitly from LTS channel. The version argument will be set to "latest" by default. + // If "dotnet-version" is specified as *, x or X resolve latest version of .NET explicitly from LTS channel. The version argument will default to "latest" by install-dotnet script. this.resolvedArgument.value = 'LTS'; } this.resolvedArgument.qualityFlag = @@ -377,6 +377,7 @@ class DotnetCoreInstaller { } installDotnet() { return __awaiter(this, void 0, void 0, function* () { + const listOfInstalledVersions = yield this.getListOfInstalledVersions(); const windowsDefaultOptions = [ '-NoLogo', '-Sta', @@ -433,17 +434,21 @@ class DotnetCoreInstaller { if (exitCode) { throw new Error(`Failed to install dotnet, exit code: ${exitCode}. ${stderr}`); } - return this.outputDotnetVersion(dotnetVersion.value); + return this.outputDotnetVersion(listOfInstalledVersions); }); } - outputDotnetVersion(version) { + getListOfInstalledVersions() { return __awaiter(this, void 0, void 0, function* () { const installationPath = process.env['DOTNET_INSTALL_DIR']; - const versionsOnRunner = yield (0, promises_1.readdir)(path_1.default.join(installationPath.replace(/'/g, ''), 'sdk')); - const installedVersion = semver_1.default.maxSatisfying(versionsOnRunner, version, { - includePrerelease: true - }); - return installedVersion; + const versionsOnRunner = (yield (0, promises_1.readdir)(path_1.default.join(installationPath.replace(/'/g, ''), 'sdk'))).filter((el) => semver_1.default.valid(el)); + return versionsOnRunner; + }); + } + outputDotnetVersion(listOfInstalledVersions) { + return __awaiter(this, void 0, void 0, function* () { + const updatedListOfInstalledVersions = yield this.getListOfInstalledVersions(); + const installedVersion = updatedListOfInstalledVersions.filter((el) => !listOfInstalledVersions.includes(el)); + return installedVersion[0]; }); } } diff --git a/src/installer.ts b/src/installer.ts index a105fef..7663de9 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -79,7 +79,7 @@ export class DotnetVersionResolver { } else if (this.isNumericTag(major)) { this.resolvedArgument.value = await this.getLatestByMajorTag(major); } else { - // If "dotnet-version" is specified as *, x or X resolve latest version of .NET explicitly from LTS channel. The version argument will be set to "latest" by default. + // If "dotnet-version" is specified as *, x or X resolve latest version of .NET explicitly from LTS channel. The version argument will default to "latest" by install-dotnet script. this.resolvedArgument.value = 'LTS'; } this.resolvedArgument.qualityFlag = @@ -199,6 +199,7 @@ export class DotnetCoreInstaller { } public async installDotnet(): Promise { + const listOfInstalledVersions = await this.getListOfInstalledVersions(); const windowsDefaultOptions = [ '-NoLogo', '-Sta', @@ -268,20 +269,20 @@ export class DotnetCoreInstaller { `Failed to install dotnet, exit code: ${exitCode}. ${stderr}` ); } - - return this.outputDotnetVersion(dotnetVersion.value); + return await this.outputDotnetVersion(listOfInstalledVersions); } - private async outputDotnetVersion(version): Promise { + private async getListOfInstalledVersions(): Promise { const installationPath = process.env['DOTNET_INSTALL_DIR']!; - const versionsOnRunner: string[] = await readdir( + const versionsOnRunner: string[] = (await readdir( path.join(installationPath.replace(/'/g, ''), 'sdk') - ); + )).filter((el) => semver.valid(el)); + return versionsOnRunner; + } - const installedVersion = semver.maxSatisfying(versionsOnRunner, version, { - includePrerelease: true - })!; - - return installedVersion; + private async outputDotnetVersion(listOfInstalledVersions: string[]): Promise { + const updatedListOfInstalledVersions = await this.getListOfInstalledVersions(); + const installedVersion = updatedListOfInstalledVersions.filter((el) => !listOfInstalledVersions.includes(el)) + return installedVersion[0]; } } From c5a57b219c805c87cd7a98b4ae0e64c541f1352e Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Fri, 5 May 2023 10:44:54 +0200 Subject: [PATCH 19/32] Update unit-tests --- __tests__/installer.test.ts | 332 ++++++++++++++++++------------------ src/installer.ts | 17 +- 2 files changed, 177 insertions(+), 172 deletions(-) diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 4830e7a..f84e303 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -53,59 +53,59 @@ describe('DotnetCoreInstaller tests', () => { } }, 30000); - // it('Aquires multiple versions of dotnet', async () => { - // const versions = ['2.2.207', '3.1.120']; + it('Aquires multiple versions of dotnet', async () => { + const versions = ['2.2.207', '3.1.120']; - // for (const version of versions) { - // await getDotnet(version); - // } - // expect(fs.existsSync(path.join(toolDir, 'sdk', '2.2.207'))).toBe(true); - // expect(fs.existsSync(path.join(toolDir, 'sdk', '3.1.120'))).toBe(true); + for (const version of versions) { + await getDotnet(version); + } + expect(fs.existsSync(path.join(toolDir, 'sdk', '2.2.207'))).toBe(true); + expect(fs.existsSync(path.join(toolDir, 'sdk', '3.1.120'))).toBe(true); - // if (IS_WINDOWS) { - // expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); - // } else { - // expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); - // } + if (IS_WINDOWS) { + expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); + } else { + expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); + } - // expect(process.env.DOTNET_ROOT).toBeDefined(); - // expect(process.env.PATH).toBeDefined(); - // expect(process.env.DOTNET_ROOT).toBe(toolDir); - // expect(process.env.PATH?.startsWith(toolDir)).toBe(true); - // }, 600000); + expect(process.env.DOTNET_ROOT).toBeDefined(); + expect(process.env.PATH).toBeDefined(); + expect(process.env.DOTNET_ROOT).toBe(toolDir); + expect(process.env.PATH?.startsWith(toolDir)).toBe(true); + }, 600000); - // it('Acquires version of dotnet if no matching version is installed', async () => { - // await getDotnet('3.1.201'); - // expect(fs.existsSync(path.join(toolDir, 'sdk', '3.1.201'))).toBe(true); - // if (IS_WINDOWS) { - // expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); - // } else { - // expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); - // } + it('Acquires version of dotnet if no matching version is installed', async () => { + await getDotnet('3.1.201'); + expect(fs.existsSync(path.join(toolDir, 'sdk', '3.1.201'))).toBe(true); + if (IS_WINDOWS) { + expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); + } else { + expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); + } - // expect(process.env.DOTNET_ROOT).toBeDefined(); - // expect(process.env.PATH).toBeDefined(); - // expect(process.env.DOTNET_ROOT).toBe(toolDir); - // expect(process.env.PATH?.startsWith(toolDir)).toBe(true); - // }, 600000); //This needs some time to download on "slower" internet connections + expect(process.env.DOTNET_ROOT).toBeDefined(); + expect(process.env.PATH).toBeDefined(); + expect(process.env.DOTNET_ROOT).toBe(toolDir); + expect(process.env.PATH?.startsWith(toolDir)).toBe(true); + }, 600000); //This needs some time to download on "slower" internet connections - // it('Acquires generic version of dotnet if no matching version is installed', async () => { - // await getDotnet('3.1'); - // const directory = fs - // .readdirSync(path.join(toolDir, 'sdk')) - // .filter(fn => fn.startsWith('3.1.')); - // expect(directory.length > 0).toBe(true); - // if (IS_WINDOWS) { - // expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); - // } else { - // expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); - // } + it('Acquires generic version of dotnet if no matching version is installed', async () => { + await getDotnet('3.1'); + const directory = fs + .readdirSync(path.join(toolDir, 'sdk')) + .filter(fn => fn.startsWith('3.1.')); + expect(directory.length > 0).toBe(true); + if (IS_WINDOWS) { + expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); + } else { + expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); + } - // expect(process.env.DOTNET_ROOT).toBeDefined(); - // expect(process.env.PATH).toBeDefined(); - // expect(process.env.DOTNET_ROOT).toBe(toolDir); - // expect(process.env.PATH?.startsWith(toolDir)).toBe(true); - // }, 600000); //This needs some time to download on "slower" internet connections + expect(process.env.DOTNET_ROOT).toBeDefined(); + expect(process.env.PATH).toBeDefined(); + expect(process.env.DOTNET_ROOT).toBe(toolDir); + expect(process.env.PATH?.startsWith(toolDir)).toBe(true); + }, 600000); //This needs some time to download on "slower" internet connections it('Returns string with installed SDK version', async () => { const version = '6.0.1xx'; @@ -116,139 +116,139 @@ describe('DotnetCoreInstaller tests', () => { }, 600000); }); -// describe('DotnetVersionResolver tests', () => { -// each([ -// '3.1', -// '3.x', -// '3.1.x', -// '3.1.*', -// '3.1.X', -// '3.1.2', -// '3.1.0-preview1' -// ]).test( -// "if valid version: '%s' is supplied, it should return version object with some value", -// async version => { -// const dotnetVersionResolver = new installer.DotnetVersionResolver( -// version -// ); -// const versionObject = await dotnetVersionResolver.createDotNetVersion(); +describe('DotnetVersionResolver tests', () => { + each([ + '3.1', + '3.x', + '3.1.x', + '3.1.*', + '3.1.X', + '3.1.2', + '3.1.0-preview1' + ]).test( + "if valid version: '%s' is supplied, it should return version object with some value", + async version => { + const dotnetVersionResolver = new installer.DotnetVersionResolver( + version + ); + const versionObject = await dotnetVersionResolver.createDotNetVersion(); -// expect(!!versionObject.value).toBe(true); -// } -// ); + expect(!!versionObject.value).toBe(true); + } + ); -// each([ -// '.', -// '..', -// ' . ', -// '. ', -// ' .', -// ' . . ', -// ' .. ', -// ' . ', -// '-1.-1', -// '-1', -// '-1.-1.-1', -// '..3', -// '1..3', -// '1..', -// '.2.3', -// '.2.x', -// '*.', -// '1.2.', -// '1.2.-abc', -// 'a.b', -// 'a.b.c', -// 'a.b.c-preview', -// ' 0 . 1 . 2 ', -// 'invalid' -// ]).test( -// "if invalid version: '%s' is supplied, it should throw", -// async version => { -// const dotnetVersionResolver = new installer.DotnetVersionResolver( -// version -// ); + each([ + '.', + '..', + ' . ', + '. ', + ' .', + ' . . ', + ' .. ', + ' . ', + '-1.-1', + '-1', + '-1.-1.-1', + '..3', + '1..3', + '1..', + '.2.3', + '.2.x', + '*.', + '1.2.', + '1.2.-abc', + 'a.b', + 'a.b.c', + 'a.b.c-preview', + ' 0 . 1 . 2 ', + 'invalid' + ]).test( + "if invalid version: '%s' is supplied, it should throw", + async version => { + const dotnetVersionResolver = new installer.DotnetVersionResolver( + version + ); -// await expect( -// async () => await dotnetVersionResolver.createDotNetVersion() -// ).rejects.toThrow(); -// } -// ); + await expect( + async () => await dotnetVersionResolver.createDotNetVersion() + ).rejects.toThrow(); + } + ); -// each(['3.1', '3.1.x', '3.1.*', '3.1.X', '5.0.1xx']).test( -// "if version: '%s' that can be resolved to 'channel' option is supplied, it should set type to 'channel' in version object", -// async version => { -// const dotnetVersionResolver = new installer.DotnetVersionResolver( -// version -// ); -// const versionObject = await dotnetVersionResolver.createDotNetVersion(); + each(['3.1', '3.1.x', '3.1.*', '3.1.X', '5.0.1xx']).test( + "if version: '%s' that can be resolved to 'channel' option is supplied, it should set type to 'channel' in version object", + async version => { + const dotnetVersionResolver = new installer.DotnetVersionResolver( + version + ); + const versionObject = await dotnetVersionResolver.createDotNetVersion(); -// expect(versionObject.type.toLowerCase().includes('channel')).toBe(true); -// } -// ); + expect(versionObject.type.toLowerCase().includes('channel')).toBe(true); + } + ); -// each(['6.0', '6.0.x', '6.0.*', '6.0.X', '6.0.1xx']).test( -// "if version: '%s' that can be resolved to 'channel' option is supplied and its major tag is >= 6, it should set type to 'channel' and qualityFlag to 'true' in version object", -// async version => { -// const dotnetVersionResolver = new installer.DotnetVersionResolver( -// version -// ); -// const versionObject = await dotnetVersionResolver.createDotNetVersion(); + each(['6.0', '6.0.x', '6.0.*', '6.0.X', '6.0.1xx']).test( + "if version: '%s' that can be resolved to 'channel' option is supplied and its major tag is >= 6, it should set type to 'channel' and qualityFlag to 'true' in version object", + async version => { + const dotnetVersionResolver = new installer.DotnetVersionResolver( + version + ); + const versionObject = await dotnetVersionResolver.createDotNetVersion(); -// expect(versionObject.type.toLowerCase().includes('channel')).toBe(true); -// expect(versionObject.qualityFlag).toBe(true); -// } -// ); + expect(versionObject.type.toLowerCase().includes('channel')).toBe(true); + expect(versionObject.qualityFlag).toBe(true); + } + ); -// each(['3.1.2', '3.1.0-preview1']).test( -// "if version: '%s' that can be resolved to 'version' option is supplied, it should set quality flag to 'false' and type to 'version' in version object", -// async version => { -// const dotnetVersionResolver = new installer.DotnetVersionResolver( -// version -// ); -// const versionObject = await dotnetVersionResolver.createDotNetVersion(); + each(['3.1.2', '3.1.0-preview1']).test( + "if version: '%s' that can be resolved to 'version' option is supplied, it should set quality flag to 'false' and type to 'version' in version object", + async version => { + const dotnetVersionResolver = new installer.DotnetVersionResolver( + version + ); + const versionObject = await dotnetVersionResolver.createDotNetVersion(); -// expect(versionObject.type.toLowerCase().includes('version')).toBe(true); -// expect(versionObject.qualityFlag).toBe(false); -// } -// ); + expect(versionObject.type.toLowerCase().includes('version')).toBe(true); + expect(versionObject.qualityFlag).toBe(false); + } + ); -// each(['3.1.2', '3.1']).test( -// 'it should create proper line arguments for powershell/bash installation scripts', -// async version => { -// const dotnetVersionResolver = new installer.DotnetVersionResolver( -// version -// ); -// const versionObject = await dotnetVersionResolver.createDotNetVersion(); -// const windowsRegEx = new RegExp(/^-[VC]/); -// const nonWindowsRegEx = new RegExp(/^--[vc]/); + each(['3.1.2', '3.1']).test( + 'it should create proper line arguments for powershell/bash installation scripts', + async version => { + const dotnetVersionResolver = new installer.DotnetVersionResolver( + version + ); + const versionObject = await dotnetVersionResolver.createDotNetVersion(); + const windowsRegEx = new RegExp(/^-[VC]/); + const nonWindowsRegEx = new RegExp(/^--[vc]/); -// if (IS_WINDOWS) { -// expect(windowsRegEx.test(versionObject.type)).toBe(true); -// expect(nonWindowsRegEx.test(versionObject.type)).toBe(false); -// } else { -// expect(nonWindowsRegEx.test(versionObject.type)).toBe(true); -// expect(windowsRegEx.test(versionObject.type)).toBe(false); -// } -// } -// ); + if (IS_WINDOWS) { + expect(windowsRegEx.test(versionObject.type)).toBe(true); + expect(nonWindowsRegEx.test(versionObject.type)).toBe(false); + } else { + expect(nonWindowsRegEx.test(versionObject.type)).toBe(true); + expect(windowsRegEx.test(versionObject.type)).toBe(false); + } + } + ); -// it('Should throw if supplied dotnet version is in A.B.Cxx syntax and the major tag is lower than 5', async () => { -// const version = '3.1.1xx'; -// const dotnetVersionResolver = new installer.DotnetVersionResolver(version); -// await expect(dotnetVersionResolver.createDotNetVersion()).rejects.toThrow( -// `'dotnet-version' was supplied in invalid format: ${version}! The A.B.Cxx syntax is available since the .NET 5.0 release.` -// ); -// }, 600000); + it('Should throw if supplied dotnet version is in A.B.Cxx syntax and the major tag is lower than 5', async () => { + const version = '3.1.1xx'; + const dotnetVersionResolver = new installer.DotnetVersionResolver(version); + await expect(dotnetVersionResolver.createDotNetVersion()).rejects.toThrow( + `'dotnet-version' was supplied in invalid format: ${version}! The A.B.Cxx syntax is available since the .NET 5.0 release.` + ); + }, 600000); -// it('Should resolve version supplied as * to channel type and set value to LTS', async () => { -// const version = '*'; -// const dotnetVersionResolver = new installer.DotnetVersionResolver(version); -// const versionObject = await dotnetVersionResolver.createDotNetVersion(); -// expect(versionObject.type.toLowerCase().includes('channel')).toBe(true); -// expect(versionObject.value).toBe('LTS'); -// }, 600000); -// }); + it('Should resolve version supplied as * to channel type and set value to LTS', async () => { + const version = '*'; + const dotnetVersionResolver = new installer.DotnetVersionResolver(version); + const versionObject = await dotnetVersionResolver.createDotNetVersion(); + expect(versionObject.type.toLowerCase().includes('channel')).toBe(true); + expect(versionObject.value).toBe('LTS'); + }, 600000); +}); function normalizeFileContents(contents: string): string { return contents diff --git a/src/installer.ts b/src/installer.ts index 7663de9..b8e628f 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -274,15 +274,20 @@ export class DotnetCoreInstaller { private async getListOfInstalledVersions(): Promise { const installationPath = process.env['DOTNET_INSTALL_DIR']!; - const versionsOnRunner: string[] = (await readdir( - path.join(installationPath.replace(/'/g, ''), 'sdk') - )).filter((el) => semver.valid(el)); + const versionsOnRunner: string[] = ( + await readdir(path.join(installationPath.replace(/'/g, ''), 'sdk')) + ).filter(el => semver.valid(el)); return versionsOnRunner; } - private async outputDotnetVersion(listOfInstalledVersions: string[]): Promise { - const updatedListOfInstalledVersions = await this.getListOfInstalledVersions(); - const installedVersion = updatedListOfInstalledVersions.filter((el) => !listOfInstalledVersions.includes(el)) + private async outputDotnetVersion( + listOfInstalledVersions: string[] + ): Promise { + const updatedListOfInstalledVersions = + await this.getListOfInstalledVersions(); + const installedVersion = updatedListOfInstalledVersions.filter( + el => !listOfInstalledVersions.includes(el) + ); return installedVersion[0]; } } From 0bc43909e03ff13fcbfd1901bd7fe6084e064162 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Mon, 15 May 2023 11:45:07 +0200 Subject: [PATCH 20/32] Update mechanic of outputting installed dotnet version --- src/installer.ts | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/installer.ts b/src/installer.ts index b8e628f..a12529f 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -4,7 +4,6 @@ import * as exec from '@actions/exec'; import * as io from '@actions/io'; import * as hc from '@actions/http-client'; import {chmodSync} from 'fs'; -import {readdir} from 'fs/promises'; import path from 'path'; import os from 'os'; import semver from 'semver'; @@ -199,7 +198,6 @@ export class DotnetCoreInstaller { } public async installDotnet(): Promise { - const listOfInstalledVersions = await this.getListOfInstalledVersions(); const windowsDefaultOptions = [ '-NoLogo', '-Sta', @@ -259,7 +257,7 @@ export class DotnetCoreInstaller { ignoreReturnCode: true, env: process.env as {string: string} }; - const {exitCode, stderr} = await exec.getExecOutput( + const {exitCode, stdout, stderr} = await exec.getExecOutput( `"${scriptPath}"`, scriptArguments, getExecOutputOptions @@ -269,25 +267,19 @@ export class DotnetCoreInstaller { `Failed to install dotnet, exit code: ${exitCode}. ${stderr}` ); } - return await this.outputDotnetVersion(listOfInstalledVersions); + + return this.parseInstalledVersion(stdout); } - private async getListOfInstalledVersions(): Promise { - const installationPath = process.env['DOTNET_INSTALL_DIR']!; - const versionsOnRunner: string[] = ( - await readdir(path.join(installationPath.replace(/'/g, ''), 'sdk')) - ).filter(el => semver.valid(el)); - return versionsOnRunner; - } + private parseInstalledVersion(stdout: string): string { + const regex = /(?\d+\.\d+\.\d+[a-z0-9._-]*)/gm; + const matchedResult = regex.exec(stdout); - private async outputDotnetVersion( - listOfInstalledVersions: string[] - ): Promise { - const updatedListOfInstalledVersions = - await this.getListOfInstalledVersions(); - const installedVersion = updatedListOfInstalledVersions.filter( - el => !listOfInstalledVersions.includes(el) - ); - return installedVersion[0]; + if (!matchedResult) { + throw new Error( + `Failed to parse installed by the script version of .NET` + ); + } + return matchedResult.groups!.version; } } From fbdbede901202ddf504297fd4db70888bbb69e10 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Mon, 15 May 2023 11:49:01 +0200 Subject: [PATCH 21/32] Rebuild solution --- dist/index.js | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/dist/index.js b/dist/index.js index 7346d82..51fc3a2 100644 --- a/dist/index.js +++ b/dist/index.js @@ -238,7 +238,6 @@ const exec = __importStar(__nccwpck_require__(1514)); const io = __importStar(__nccwpck_require__(7436)); const hc = __importStar(__nccwpck_require__(6255)); const fs_1 = __nccwpck_require__(7147); -const promises_1 = __nccwpck_require__(3292); const path_1 = __importDefault(__nccwpck_require__(1017)); const os_1 = __importDefault(__nccwpck_require__(2037)); const semver_1 = __importDefault(__nccwpck_require__(5911)); @@ -377,7 +376,6 @@ class DotnetCoreInstaller { } installDotnet() { return __awaiter(this, void 0, void 0, function* () { - const listOfInstalledVersions = yield this.getListOfInstalledVersions(); const windowsDefaultOptions = [ '-NoLogo', '-Sta', @@ -430,26 +428,20 @@ class DotnetCoreInstaller { ignoreReturnCode: true, env: process.env }; - const { exitCode, stderr } = yield exec.getExecOutput(`"${scriptPath}"`, scriptArguments, getExecOutputOptions); + const { exitCode, stdout, stderr } = yield exec.getExecOutput(`"${scriptPath}"`, scriptArguments, getExecOutputOptions); if (exitCode) { throw new Error(`Failed to install dotnet, exit code: ${exitCode}. ${stderr}`); } - return this.outputDotnetVersion(listOfInstalledVersions); + return this.parseInstalledVersion(stdout); }); } - getListOfInstalledVersions() { - return __awaiter(this, void 0, void 0, function* () { - const installationPath = process.env['DOTNET_INSTALL_DIR']; - const versionsOnRunner = (yield (0, promises_1.readdir)(path_1.default.join(installationPath.replace(/'/g, ''), 'sdk'))).filter((el) => semver_1.default.valid(el)); - return versionsOnRunner; - }); - } - outputDotnetVersion(listOfInstalledVersions) { - return __awaiter(this, void 0, void 0, function* () { - const updatedListOfInstalledVersions = yield this.getListOfInstalledVersions(); - const installedVersion = updatedListOfInstalledVersions.filter((el) => !listOfInstalledVersions.includes(el)); - return installedVersion[0]; - }); + parseInstalledVersion(stdout) { + const regex = /(?\d+\.\d+\.\d+[a-z0-9._-]*)/gm; + const matchedResult = regex.exec(stdout); + if (!matchedResult) { + throw new Error(`Failed to parse installed by the script version of .NET`); + } + return matchedResult.groups.version; } } exports.DotnetCoreInstaller = DotnetCoreInstaller; @@ -21120,14 +21112,6 @@ module.exports = require("fs"); /***/ }), -/***/ 3292: -/***/ ((module) => { - -"use strict"; -module.exports = require("fs/promises"); - -/***/ }), - /***/ 3685: /***/ ((module) => { From 0f534f5829b2e991ed7d67169d882659f921a60d Mon Sep 17 00:00:00 2001 From: Ivan <98037481+IvanZosimov@users.noreply.github.com> Date: Mon, 15 May 2023 14:09:29 +0200 Subject: [PATCH 22/32] Refactor and update unit-tests (#418) * Update unit-tests and dotnet-install scripts --- .github/workflows/e2e-tests.yml | 2 +- __tests__/__snapshots__/authutil.test.ts.snap | 22 +- __tests__/authutil.test.ts | 56 +- __tests__/csc.test.ts | 42 +- __tests__/installation-scripts.test.ts | 52 ++ __tests__/installer.test.ts | 629 ++++++++++-------- __tests__/setup-dotnet.test.ts | 204 +++--- externals/install-dotnet.ps1 | 459 ++++++------- externals/install-dotnet.sh | 14 +- 9 files changed, 853 insertions(+), 627 deletions(-) create mode 100644 __tests__/installation-scripts.test.ts diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 9963bbc..3ec658b 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -24,7 +24,7 @@ jobs: - name: Clear toolcache shell: pwsh run: __tests__/clear-toolcache.ps1 ${{ runner.os }} - - name: Setup dotnet 2.2.402 and 3.1.404 + - name: Setup dotnet 2.2.402, 3.1.404 and 3.0.x uses: ./ with: dotnet-version: | diff --git a/__tests__/__snapshots__/authutil.test.ts.snap b/__tests__/__snapshots__/authutil.test.ts.snap index d310f14..0ea506b 100644 --- a/__tests__/__snapshots__/authutil.test.ts.snap +++ b/__tests__/__snapshots__/authutil.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`authutil tests Existing config not in repo root, sets up a partial NuGet.config user/PAT for GPR 1`] = ` +exports[`authutil tests existing config not in repo root, sets up a partial NuGet.config user/PAT for GPR 1`] = ` " @@ -15,7 +15,7 @@ exports[`authutil tests Existing config not in repo root, sets up a partial NuGe " `; -exports[`authutil tests Existing config w/ Azure Artifacts source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR 1`] = ` +exports[`authutil tests existing config w/ Azure Artifacts source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR 1`] = ` " @@ -30,7 +30,7 @@ exports[`authutil tests Existing config w/ Azure Artifacts source and NuGet.org, " `; -exports[`authutil tests Existing config w/ GPR source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR 1`] = ` +exports[`authutil tests existing config w/ GPR source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR 1`] = ` " @@ -45,7 +45,7 @@ exports[`authutil tests Existing config w/ GPR source and NuGet.org, sets up a p " `; -exports[`authutil tests Existing config w/ no GPR sources, sets up a full NuGet.config with URL and user/PAT for GPR 1`] = ` +exports[`authutil tests existing config w/ no GPR sources, sets up a full NuGet.config with URL and user/PAT for GPR 1`] = ` " @@ -63,7 +63,7 @@ exports[`authutil tests Existing config w/ no GPR sources, sets up a full NuGet. " `; -exports[`authutil tests Existing config w/ no sources, sets up a full NuGet.config with URL and user/PAT for GPR 1`] = ` +exports[`authutil tests existing config w/ no sources, sets up a full NuGet.config with URL and user/PAT for GPR 1`] = ` " @@ -81,7 +81,7 @@ exports[`authutil tests Existing config w/ no sources, sets up a full NuGet.conf " `; -exports[`authutil tests Existing config w/ only Azure Artifacts source, sets up a partial NuGet.config user/PAT for GPR 1`] = ` +exports[`authutil tests existing config w/ only Azure Artifacts source, sets up a partial NuGet.config user/PAT for GPR 1`] = ` " @@ -96,7 +96,7 @@ exports[`authutil tests Existing config w/ only Azure Artifacts source, sets up " `; -exports[`authutil tests Existing config w/ only GPR source, sets up a partial NuGet.config user/PAT for GPR 1`] = ` +exports[`authutil tests existing config w/ only GPR source, sets up a partial NuGet.config user/PAT for GPR 1`] = ` " @@ -111,7 +111,7 @@ exports[`authutil tests Existing config w/ only GPR source, sets up a partial Nu " `; -exports[`authutil tests Existing config w/ two GPR sources, sets up a partial NuGet.config user/PAT for GPR 1`] = ` +exports[`authutil tests existing config w/ two GPR sources, sets up a partial NuGet.config user/PAT for GPR 1`] = ` " @@ -130,7 +130,7 @@ exports[`authutil tests Existing config w/ two GPR sources, sets up a partial Nu " `; -exports[`authutil tests No existing config, sets up a full NuGet.config with URL and other owner/PAT for GPR 1`] = ` +exports[`authutil tests no existing config, sets up a full NuGet.config with URL and other owner/PAT for GPR 1`] = ` " @@ -148,7 +148,7 @@ exports[`authutil tests No existing config, sets up a full NuGet.config with URL " `; -exports[`authutil tests No existing config, sets up a full NuGet.config with URL and token for other source 1`] = ` +exports[`authutil tests no existing config, sets up a full NuGet.config with URL and token for other source 1`] = ` " @@ -166,7 +166,7 @@ exports[`authutil tests No existing config, sets up a full NuGet.config with URL " `; -exports[`authutil tests No existing config, sets up a full NuGet.config with URL and user/PAT for GPR 1`] = ` +exports[`authutil tests no existing config, sets up a full NuGet.config with URL and user/PAT for GPR 1`] = ` " diff --git a/__tests__/authutil.test.ts b/__tests__/authutil.test.ts index d9a0b7a..3435cdf 100644 --- a/__tests__/authutil.test.ts +++ b/__tests__/authutil.test.ts @@ -91,9 +91,9 @@ describe('authutil tests', () => { process.env['NUGET_AUTH_TOKEN'] = ''; }); - it('No existing config, sets up a full NuGet.config with URL and user/PAT for GPR', async () => { + it('no existing config, sets up a full NuGet.config with URL and user/PAT for GPR', async () => { process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; - await auth.configAuthentication( + auth.configAuthentication( 'https://nuget.pkg.github.com/OwnerName/index.json', '', fakeSourcesDirForTesting @@ -104,10 +104,10 @@ describe('authutil tests', () => { ).toMatchSnapshot(); }); - it('No existing config, auth token environment variable not provided, throws', async () => { + it('no existing config, auth token environment variable not provided, throws', async () => { let thrown = false; try { - await auth.configAuthentication( + auth.configAuthentication( 'https://nuget.pkg.github.com/OwnerName/index.json', '', fakeSourcesDirForTesting @@ -118,10 +118,10 @@ describe('authutil tests', () => { expect(thrown).toBe(true); }); - it('No existing config, sets up a full NuGet.config with URL and other owner/PAT for GPR', async () => { + it('no existing config, sets up a full NuGet.config with URL and other owner/PAT for GPR', async () => { process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; process.env['INPUT_OWNER'] = 'otherorg'; - await auth.configAuthentication( + auth.configAuthentication( 'https://nuget.pkg.github.com/otherorg/index.json', '', fakeSourcesDirForTesting @@ -132,7 +132,7 @@ describe('authutil tests', () => { ).toMatchSnapshot(); }); - it('Existing config (invalid), tries to parse an invalid NuGet.config and throws', async () => { + it('existing config (invalid), tries to parse an invalid NuGet.config and throws', async () => { process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; const inputNuGetConfigPath: string = path.join( fakeSourcesDirForTesting, @@ -141,7 +141,7 @@ describe('authutil tests', () => { fs.writeFileSync(inputNuGetConfigPath, invalidNuGetConfig); let thrown = false; try { - await auth.configAuthentication( + auth.configAuthentication( 'https://nuget.pkg.github.com/OwnerName/index.json', '', fakeSourcesDirForTesting @@ -152,14 +152,14 @@ describe('authutil tests', () => { expect(thrown).toBe(true); }); - it('Existing config w/ no sources, sets up a full NuGet.config with URL and user/PAT for GPR', async () => { + it('existing config w/ no sources, sets up a full NuGet.config with URL and user/PAT for GPR', async () => { process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; const inputNuGetConfigPath: string = path.join( fakeSourcesDirForTesting, 'nuget.config' ); fs.writeFileSync(inputNuGetConfigPath, emptyNuGetConfig); - await auth.configAuthentication( + auth.configAuthentication( 'https://nuget.pkg.github.com/OwnerName/index.json', '', fakeSourcesDirForTesting @@ -170,14 +170,14 @@ describe('authutil tests', () => { ).toMatchSnapshot(); }); - it('Existing config w/ no GPR sources, sets up a full NuGet.config with URL and user/PAT for GPR', async () => { + it('existing config w/ no GPR sources, sets up a full NuGet.config with URL and user/PAT for GPR', async () => { process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; const inputNuGetConfigPath: string = path.join( fakeSourcesDirForTesting, 'nuget.config' ); fs.writeFileSync(inputNuGetConfigPath, nugetorgNuGetConfig); - await auth.configAuthentication( + auth.configAuthentication( 'https://nuget.pkg.github.com/OwnerName/index.json', '', fakeSourcesDirForTesting @@ -188,14 +188,14 @@ describe('authutil tests', () => { ).toMatchSnapshot(); }); - it('Existing config w/ only GPR source, sets up a partial NuGet.config user/PAT for GPR', async () => { + it('existing config w/ only GPR source, sets up a partial NuGet.config user/PAT for GPR', async () => { process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; const inputNuGetConfigPath: string = path.join( fakeSourcesDirForTesting, 'nuget.config' ); fs.writeFileSync(inputNuGetConfigPath, gprNuGetConfig); - await auth.configAuthentication( + auth.configAuthentication( 'https://nuget.pkg.github.com/OwnerName/index.json', '', fakeSourcesDirForTesting @@ -206,14 +206,14 @@ describe('authutil tests', () => { ).toMatchSnapshot(); }); - it('Existing config w/ GPR source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR', async () => { + it('existing config w/ GPR source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR', async () => { process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; const inputNuGetConfigPath: string = path.join( fakeSourcesDirForTesting, 'nuget.config' ); fs.writeFileSync(inputNuGetConfigPath, gprnugetorgNuGetConfig); - await auth.configAuthentication( + auth.configAuthentication( 'https://nuget.pkg.github.com/OwnerName/index.json', '', fakeSourcesDirForTesting @@ -224,14 +224,14 @@ describe('authutil tests', () => { ).toMatchSnapshot(); }); - it('Existing config w/ two GPR sources, sets up a partial NuGet.config user/PAT for GPR', async () => { + it('existing config w/ two GPR sources, sets up a partial NuGet.config user/PAT for GPR', async () => { process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; const inputNuGetConfigPath: string = path.join( fakeSourcesDirForTesting, 'nuget.config' ); fs.writeFileSync(inputNuGetConfigPath, twogprNuGetConfig); - await auth.configAuthentication( + auth.configAuthentication( 'https://nuget.pkg.github.com', '', fakeSourcesDirForTesting @@ -242,7 +242,7 @@ describe('authutil tests', () => { ).toMatchSnapshot(); }); - it('Existing config w/ spaces in key, throws for now', async () => { + it('existing config w/ spaces in key, throws for now', async () => { process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; const inputNuGetConfigPath: string = path.join( fakeSourcesDirForTesting, @@ -251,7 +251,7 @@ describe('authutil tests', () => { fs.writeFileSync(inputNuGetConfigPath, spaceNuGetConfig); let thrown = false; try { - await auth.configAuthentication( + auth.configAuthentication( 'https://nuget.pkg.github.com/OwnerName/index.json', '', fakeSourcesDirForTesting @@ -262,7 +262,7 @@ describe('authutil tests', () => { expect(thrown).toBe(true); }); - it('Existing config not in repo root, sets up a partial NuGet.config user/PAT for GPR', async () => { + it('existing config not in repo root, sets up a partial NuGet.config user/PAT for GPR', async () => { process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; const inputNuGetConfigDirectory: string = path.join( fakeSourcesDirForTesting, @@ -274,7 +274,7 @@ describe('authutil tests', () => { ); fs.mkdirSync(inputNuGetConfigDirectory, {recursive: true}); fs.writeFileSync(inputNuGetConfigPath, gprNuGetConfig); - await auth.configAuthentication( + auth.configAuthentication( 'https://nuget.pkg.github.com/OwnerName/index.json', 'subfolder/nuget.config', fakeSourcesDirForTesting @@ -285,14 +285,14 @@ describe('authutil tests', () => { ).toMatchSnapshot(); }); - it('Existing config w/ only Azure Artifacts source, sets up a partial NuGet.config user/PAT for GPR', async () => { + it('existing config w/ only Azure Artifacts source, sets up a partial NuGet.config user/PAT for GPR', async () => { process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; const inputNuGetConfigPath: string = path.join( fakeSourcesDirForTesting, 'nuget.config' ); fs.writeFileSync(inputNuGetConfigPath, azureartifactsNuGetConfig); - await auth.configAuthentication( + auth.configAuthentication( 'https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json', '', fakeSourcesDirForTesting @@ -303,14 +303,14 @@ describe('authutil tests', () => { ).toMatchSnapshot(); }); - it('Existing config w/ Azure Artifacts source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR', async () => { + it('existing config w/ Azure Artifacts source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR', async () => { process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; const inputNuGetConfigPath: string = path.join( fakeSourcesDirForTesting, 'nuget.config' ); fs.writeFileSync(inputNuGetConfigPath, azureartifactsnugetorgNuGetConfig); - await auth.configAuthentication( + auth.configAuthentication( 'https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json', '', fakeSourcesDirForTesting @@ -321,9 +321,9 @@ describe('authutil tests', () => { ).toMatchSnapshot(); }); - it('No existing config, sets up a full NuGet.config with URL and token for other source', async () => { + it('no existing config, sets up a full NuGet.config with URL and token for other source', async () => { process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; - await auth.configAuthentication( + auth.configAuthentication( 'https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json', '', fakeSourcesDirForTesting diff --git a/__tests__/csc.test.ts b/__tests__/csc.test.ts index 85433b7..8d43b39 100644 --- a/__tests__/csc.test.ts +++ b/__tests__/csc.test.ts @@ -1,21 +1,45 @@ import cscFile from '../.github/csc.json'; describe('csc tests', () => { - it('Valid regular expression', async () => { - const regex = cscFile['problemMatcher'][0]['pattern'][0]['regexp']; + test('regular expression in csc.json is valid', async () => { + const regexPattern = cscFile['problemMatcher'][0]['pattern'][0]['regexp']; + const regexResultsMap = cscFile['problemMatcher'][0]['pattern'][0]; - console.log(regex); - const re = new RegExp(regex); + const regex = new RegExp(regexPattern); - // Ideally we would verify that this const stringsToMatch = [ 'Program.cs(10,79): error CS1002: ; expected [/Users/zacharyeisinger/Documents/repo/setup-dotnet/__tests__/sample-broken-csproj/sample.csproj]', "S:\\Msbuild\\src\\Build\\Evaluation\\ExpressionShredder.cs(33,7): error CS1003: Syntax error, ',' expected [S:\\msbuild\\src\\Build\\Microsoft.Build.csproj > Properties:prop]" ]; + // Expected results are calculated according to the csc matcher located in csc.json file + const expectedResults = [ + { + file: 'Program.cs', + line: '10', + severity: 'error', + code: 'CS1002', + message: '; expected', + fromPath: + '/Users/zacharyeisinger/Documents/repo/setup-dotnet/__tests__/sample-broken-csproj/sample.csproj' + }, + { + file: 'S:\\Msbuild\\src\\Build\\Evaluation\\ExpressionShredder.cs', + line: '33', + severity: 'error', + code: 'CS1003', + message: "Syntax error, ',' expected", + fromPath: + 'S:\\msbuild\\src\\Build\\Microsoft.Build.csproj > Properties:prop' + } + ]; - stringsToMatch.forEach(string => { - const matchStr = string.match(re); - console.log(matchStr); - expect(matchStr).toEqual(expect.anything()); + stringsToMatch.map((string, index) => { + const matchedResultsArray = string.match(regex); + for (const propName in expectedResults[index]) { + const propertyIndex = regexResultsMap[propName]; + const expectedPropValue = expectedResults[index][propName]; + const matchedPropValue = matchedResultsArray![propertyIndex]; + expect(matchedPropValue).toEqual(expectedPropValue); + } }); }, 10000); }); diff --git a/__tests__/installation-scripts.test.ts b/__tests__/installation-scripts.test.ts new file mode 100644 index 0000000..e309a98 --- /dev/null +++ b/__tests__/installation-scripts.test.ts @@ -0,0 +1,52 @@ +import path from 'path'; +import fs from 'fs'; +import * as hc from '@actions/http-client'; + +describe('Dotnet installation scripts tests', () => { + it('Uses an up to date bash download script', async () => { + const httpCallbackClient = new hc.HttpClient('setup-dotnet-test', [], { + allowRetries: true, + maxRetries: 3 + }); + const response: hc.HttpClientResponse = await httpCallbackClient.get( + 'https://dot.net/v1/dotnet-install.sh' + ); + expect(response.message.statusCode).toBe(200); + const upToDateContents: string = await response.readBody(); + const currentContents: string = fs + .readFileSync( + path.join(__dirname, '..', 'externals', 'install-dotnet.sh') + ) + .toString(); + expect(normalizeFileContents(currentContents)).toBe( + normalizeFileContents(upToDateContents) + ); + }, 30000); + + it('Uses an up to date powershell download script', async () => { + const httpCallbackClient = new hc.HttpClient('setup-dotnet-test', [], { + allowRetries: true, + maxRetries: 3 + }); + const response: hc.HttpClientResponse = await httpCallbackClient.get( + 'https://dot.net/v1/dotnet-install.ps1' + ); + expect(response.message.statusCode).toBe(200); + const upToDateContents: string = await response.readBody(); + const currentContents: string = fs + .readFileSync( + path.join(__dirname, '..', 'externals', 'install-dotnet.ps1') + ) + .toString(); + expect(normalizeFileContents(currentContents)).toBe( + normalizeFileContents(upToDateContents) + ); + }, 30000); +}); + +function normalizeFileContents(contents: string): string { + return contents + .trim() + .replace(new RegExp('\r\n', 'g'), '\n') + .replace(new RegExp('\r', 'g'), '\n'); +} diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 1a7e024..b26d915 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -1,298 +1,399 @@ -import * as io from '@actions/io'; -import * as os from 'os'; -import fs from 'fs'; -import path from 'path'; import each from 'jest-each'; -import * as hc from '@actions/http-client'; +import semver from 'semver'; +import * as exec from '@actions/exec'; +import * as core from '@actions/core'; +import * as io from '@actions/io'; import * as installer from '../src/installer'; -import {QualityOptions} from '../src/setup-dotnet'; import {IS_WINDOWS} from '../src/utils'; -import {IS_LINUX} from '../src/utils'; +import {QualityOptions} from '../src/setup-dotnet'; -let toolDir: string; +describe('installer tests', () => { + const env = process.env; -if (IS_WINDOWS) { - toolDir = path.join(process.env['PROGRAMFILES'] + '', 'dotnet'); -} else if (IS_LINUX) { - toolDir = '/usr/share/dotnet'; -} else { - toolDir = path.join(process.env['HOME'] + '', '.dotnet'); -} -const tempDir = path.join(__dirname, 'runner', 'temp'); + beforeEach(() => { + jest.resetModules(); + process.env = {...env}; + }); -process.env['RUNNER_TOOL_CACHE'] = toolDir; -process.env['RUNNER_TEMP'] = tempDir; + describe('DotnetCoreInstaller tests', () => { + const getExecOutputSpy = jest.spyOn(exec, 'getExecOutput'); + const warningSpy = jest.spyOn(core, 'warning'); + const whichSpy = jest.spyOn(io, 'which'); + const maxSatisfyingSpy = jest.spyOn(semver, 'maxSatisfying'); -describe('DotnetCoreInstaller tests', () => { - beforeAll(async () => { - process.env.RUNNER_TOOL_CACHE = toolDir; - process.env.DOTNET_INSTALL_DIR = toolDir; - process.env.RUNNER_TEMP = tempDir; - process.env.DOTNET_ROOT = ''; - try { - await io.rmRF(`${toolDir}/*`); - await io.rmRF(`${tempDir}/*`); - } catch (err) { - console.log( - `Failed to remove test directories, check the error message:${os.EOL}`, - err.message - ); - } - }, 30000); + describe('installDotnet() tests', () => { + whichSpy.mockImplementation(() => Promise.resolve('PathToShell')); - afterEach(async () => { - try { - await io.rmRF(`${toolDir}/*`); - await io.rmRF(`${tempDir}/*`); - } catch (err) { - console.log( - `Failed to remove test directories, check the error message:${os.EOL}`, - err.message - ); - } - }, 30000); + it('should throw the error in case of non-zero exit code of the installation script. The error message should contain logs.', async () => { + const inputVersion = '3.1.100'; + const inputQuality = '' as QualityOptions; + const errorMessage = 'fictitious error message!'; + getExecOutputSpy.mockImplementation(() => { + return Promise.resolve({ + exitCode: 1, + stdout: '', + stderr: errorMessage + }); + }); + const dotnetInstaller = new installer.DotnetCoreInstaller( + inputVersion, + inputQuality + ); + await expect(dotnetInstaller.installDotnet()).rejects.toThrow( + `Failed to install dotnet, exit code: 1. ${errorMessage}` + ); + }); - it('Aquires multiple versions of dotnet', async () => { - const versions = ['2.2.207', '3.1.120']; + it('should return version of .NET SDK after installation complete', async () => { + const inputVersion = '3.1.100'; + const inputQuality = '' as QualityOptions; + getExecOutputSpy.mockImplementation(() => { + return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + }); + maxSatisfyingSpy.mockImplementation(() => inputVersion); - for (const version of versions) { - await getDotnet(version); - } - expect(fs.existsSync(path.join(toolDir, 'sdk', '2.2.207'))).toBe(true); - expect(fs.existsSync(path.join(toolDir, 'sdk', '3.1.120'))).toBe(true); + const dotnetInstaller = new installer.DotnetCoreInstaller( + inputVersion, + inputQuality + ); + const installedVersion = await dotnetInstaller.installDotnet(); - if (IS_WINDOWS) { - expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); - } else { - expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); - } + expect(installedVersion).toBe(inputVersion); + }); - expect(process.env.DOTNET_ROOT).toBeDefined(); - expect(process.env.PATH).toBeDefined(); - expect(process.env.DOTNET_ROOT).toBe(toolDir); - expect(process.env.PATH?.startsWith(toolDir)).toBe(true); - }, 600000); + it(`should supply 'version' argument to the installation script if supplied version is in A.B.C syntax`, async () => { + const inputVersion = '6.0.300'; + const inputQuality = '' as QualityOptions; - it('Acquires version of dotnet if no matching version is installed', async () => { - await getDotnet('3.1.201'); - expect(fs.existsSync(path.join(toolDir, 'sdk', '3.1.201'))).toBe(true); - if (IS_WINDOWS) { - expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); - } else { - expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); - } + getExecOutputSpy.mockImplementation(() => { + return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + }); + maxSatisfyingSpy.mockImplementation(() => inputVersion); - expect(process.env.DOTNET_ROOT).toBeDefined(); - expect(process.env.PATH).toBeDefined(); - expect(process.env.DOTNET_ROOT).toBe(toolDir); - expect(process.env.PATH?.startsWith(toolDir)).toBe(true); - }, 600000); //This needs some time to download on "slower" internet connections + const dotnetInstaller = new installer.DotnetCoreInstaller( + inputVersion, + inputQuality + ); - it('Acquires generic version of dotnet if no matching version is installed', async () => { - await getDotnet('3.1'); - const directory = fs - .readdirSync(path.join(toolDir, 'sdk')) - .filter(fn => fn.startsWith('3.1.')); - expect(directory.length > 0).toBe(true); - if (IS_WINDOWS) { - expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); - } else { - expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); - } + await dotnetInstaller.installDotnet(); - expect(process.env.DOTNET_ROOT).toBeDefined(); - expect(process.env.PATH).toBeDefined(); - expect(process.env.DOTNET_ROOT).toBe(toolDir); - expect(process.env.PATH?.startsWith(toolDir)).toBe(true); - }, 600000); //This needs some time to download on "slower" internet connections + const scriptArguments = ( + getExecOutputSpy.mock.calls[0][1] as string[] + ).join(' '); + const expectedArgument = IS_WINDOWS + ? `-Version ${inputVersion}` + : `--version ${inputVersion}`; - it('Returns string with installed SDK version', async () => { - const version = '3.1.120'; + expect(scriptArguments).toContain(expectedArgument); + }); - const installedVersion = await getDotnet(version); + it(`should warn if the 'quality' input is set and the supplied version is in A.B.C syntax`, async () => { + const inputVersion = '6.0.300'; + const inputQuality = 'ga' as QualityOptions; - expect(installedVersion).toBe('3.1.120'); - }, 600000); + getExecOutputSpy.mockImplementation(() => { + return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + }); + maxSatisfyingSpy.mockImplementation(() => inputVersion); - it('Throws if no location contains correct dotnet version', async () => { - await expect(async () => { - await getDotnet('1000.0.0'); - }).rejects.toThrow(); - }, 30000); + const dotnetInstaller = new installer.DotnetCoreInstaller( + inputVersion, + inputQuality + ); - it('Uses an up to date bash download script', async () => { - const httpCallbackClient = new hc.HttpClient('setup-dotnet-test', [], { - allowRetries: true, - maxRetries: 3 - }); - const response: hc.HttpClientResponse = await httpCallbackClient.get( - 'https://dot.net/v1/dotnet-install.sh' - ); - expect(response.message.statusCode).toBe(200); - const upToDateContents: string = await response.readBody(); - const currentContents: string = fs - .readFileSync( - path.join(__dirname, '..', 'externals', 'install-dotnet.sh') - ) - .toString(); - expect(normalizeFileContents(currentContents)).toBe( - normalizeFileContents(upToDateContents) - ); - }, 30000); + await dotnetInstaller.installDotnet(); - it('Uses an up to date powershell download script', async () => { - const httpCallbackClient = new hc.HttpClient('setup-dotnet-test', [], { - allowRetries: true, - maxRetries: 3 - }); - const response: hc.HttpClientResponse = await httpCallbackClient.get( - 'https://dot.net/v1/dotnet-install.ps1' - ); - expect(response.message.statusCode).toBe(200); - const upToDateContents: string = await response.readBody(); - const currentContents: string = fs - .readFileSync( - path.join(__dirname, '..', 'externals', 'install-dotnet.ps1') - ) - .toString(); - expect(normalizeFileContents(currentContents)).toBe( - normalizeFileContents(upToDateContents) - ); - }, 30000); -}); + expect(warningSpy).toHaveBeenCalledWith( + `'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A and A.x formats where the major tag is higher than 5. You specified: ${inputVersion}. 'dotnet-quality' input is ignored.` + ); + }); -describe('DotnetVersionResolver tests', () => { - each([ - '3.1', - '3.x', - '3.1.x', - '3.1.*', - '3.1.X', - '3.1.2', - '3.1.0-preview1' - ]).test( - "if valid version: '%s' is supplied, it should return version object with some value", - async version => { - const dotnetVersionResolver = new installer.DotnetVersionResolver( - version - ); - const versionObject = await dotnetVersionResolver.createDotNetVersion(); + it(`should warn if the 'quality' input is set and version isn't in A.B.C syntax but major tag is lower then 6`, async () => { + const inputVersion = '3.1'; + const inputQuality = 'ga' as QualityOptions; - expect(!!versionObject.value).toBe(true); - } - ); + getExecOutputSpy.mockImplementation(() => { + return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + }); + maxSatisfyingSpy.mockImplementation(() => inputVersion); - each([ - '.', - '..', - ' . ', - '. ', - ' .', - ' . . ', - ' .. ', - ' . ', - '-1.-1', - '-1', - '-1.-1.-1', - '..3', - '1..3', - '1..', - '.2.3', - '.2.x', - '*.', - '1.2.', - '1.2.-abc', - 'a.b', - 'a.b.c', - 'a.b.c-preview', - ' 0 . 1 . 2 ', - 'invalid' - ]).test( - "if invalid version: '%s' is supplied, it should throw", - async version => { - const dotnetVersionResolver = new installer.DotnetVersionResolver( - version + const dotnetInstaller = new installer.DotnetCoreInstaller( + inputVersion, + inputQuality + ); + + await dotnetInstaller.installDotnet(); + + expect(warningSpy).toHaveBeenCalledWith( + `'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A and A.x formats where the major tag is higher than 5. You specified: ${inputVersion}. 'dotnet-quality' input is ignored.` + ); + }); + + each(['6', '6.0', '6.0.x', '6.0.*', '6.0.X']).test( + `should supply 'quality' argument to the installation script if quality input is set and version (%s) is not in A.B.C syntax`, + async inputVersion => { + const inputQuality = 'ga' as QualityOptions; + const exitCode = 0; + getExecOutputSpy.mockImplementation(() => { + return Promise.resolve({ + exitCode: exitCode, + stdout: '', + stderr: '' + }); + }); + maxSatisfyingSpy.mockImplementation(() => inputVersion); + + const dotnetInstaller = new installer.DotnetCoreInstaller( + inputVersion, + inputQuality + ); + + await dotnetInstaller.installDotnet(); + + const scriptArguments = ( + getExecOutputSpy.mock.calls[0][1] as string[] + ).join(' '); + const expectedArgument = IS_WINDOWS + ? `-Quality ${inputQuality}` + : `--quality ${inputQuality}`; + + expect(scriptArguments).toContain(expectedArgument); + } ); - await expect( - async () => await dotnetVersionResolver.createDotNetVersion() - ).rejects.toThrow(); - } - ); + each(['6', '6.0', '6.0.x', '6.0.*', '6.0.X']).test( + `should supply 'channel' argument to the installation script if version (%s) isn't in A.B.C syntax`, + async inputVersion => { + const inputQuality = '' as QualityOptions; + const exitCode = 0; + getExecOutputSpy.mockImplementation(() => { + return Promise.resolve({ + exitCode: exitCode, + stdout: '', + stderr: '' + }); + }); + maxSatisfyingSpy.mockImplementation(() => inputVersion); - each(['3.1', '3.1.x', '3.1.*', '3.1.X']).test( - "if version: '%s' that can be resolved to 'channel' option is supplied, it should set type to 'channel' in version object", - async version => { - const dotnetVersionResolver = new installer.DotnetVersionResolver( - version + const dotnetInstaller = new installer.DotnetCoreInstaller( + inputVersion, + inputQuality + ); + + await dotnetInstaller.installDotnet(); + + const scriptArguments = ( + getExecOutputSpy.mock.calls[0][1] as string[] + ).join(' '); + const expectedArgument = IS_WINDOWS + ? `-Channel 6.0` + : `--channel 6.0`; + + expect(scriptArguments).toContain(expectedArgument); + } ); - const versionObject = await dotnetVersionResolver.createDotNetVersion(); - - expect(versionObject.type.toLowerCase().includes('channel')).toBe(true); - } - ); - - each(['6.0', '6.0.x', '6.0.*', '6.0.X']).test( - "if version: '%s' that can be resolved to 'channel' option is supplied and its major tag is >= 6, it should set type to 'channel' and qualityFlag to 'true' in version object", - async version => { - const dotnetVersionResolver = new installer.DotnetVersionResolver( - version - ); - const versionObject = await dotnetVersionResolver.createDotNetVersion(); - - expect(versionObject.type.toLowerCase().includes('channel')).toBe(true); - expect(versionObject.qualityFlag).toBe(true); - } - ); - - each(['3.1.2', '3.1.0-preview1']).test( - "if version: '%s' that can be resolved to 'version' option is supplied, it should set quality flag to 'false' and type to 'version' in version object", - async version => { - const dotnetVersionResolver = new installer.DotnetVersionResolver( - version - ); - const versionObject = await dotnetVersionResolver.createDotNetVersion(); - - expect(versionObject.type.toLowerCase().includes('version')).toBe(true); - expect(versionObject.qualityFlag).toBe(false); - } - ); - - each(['3.1.2', '3.1']).test( - 'it should create proper line arguments for powershell/bash installation scripts', - async version => { - const dotnetVersionResolver = new installer.DotnetVersionResolver( - version - ); - const versionObject = await dotnetVersionResolver.createDotNetVersion(); - const windowsRegEx = new RegExp(/^-[VC]/); - const nonWindowsRegEx = new RegExp(/^--[vc]/); if (IS_WINDOWS) { - expect(windowsRegEx.test(versionObject.type)).toBe(true); - expect(nonWindowsRegEx.test(versionObject.type)).toBe(false); - } else { - expect(nonWindowsRegEx.test(versionObject.type)).toBe(true); - expect(windowsRegEx.test(versionObject.type)).toBe(false); + it(`should supply '-ProxyAddress' argument to the installation script if env.variable 'https_proxy' is set`, async () => { + process.env['https_proxy'] = 'https://proxy.com'; + const inputVersion = '6.0.100'; + const inputQuality = '' as QualityOptions; + + getExecOutputSpy.mockImplementation(() => { + return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + }); + maxSatisfyingSpy.mockImplementation(() => inputVersion); + + const dotnetInstaller = new installer.DotnetCoreInstaller( + inputVersion, + inputQuality + ); + + await dotnetInstaller.installDotnet(); + + const scriptArguments = ( + getExecOutputSpy.mock.calls[0][1] as string[] + ).join(' '); + + expect(scriptArguments).toContain( + `-ProxyAddress ${process.env['https_proxy']}` + ); + }); + + it(`should supply '-ProxyBypassList' argument to the installation script if env.variable 'no_proxy' is set`, async () => { + process.env['no_proxy'] = 'first.url,second.url'; + const inputVersion = '6.0.100'; + const inputQuality = '' as QualityOptions; + + getExecOutputSpy.mockImplementation(() => { + return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + }); + maxSatisfyingSpy.mockImplementation(() => inputVersion); + + const dotnetInstaller = new installer.DotnetCoreInstaller( + inputVersion, + inputQuality + ); + + await dotnetInstaller.installDotnet(); + + const scriptArguments = ( + getExecOutputSpy.mock.calls[0][1] as string[] + ).join(' '); + + expect(scriptArguments).toContain( + `-ProxyBypassList ${process.env['no_proxy']}` + ); + }); } - } - ); + }); + + describe('addToPath() tests', () => { + it(`should export DOTNET_ROOT env.var with value from DOTNET_INSTALL_DIR env.var`, async () => { + process.env['DOTNET_INSTALL_DIR'] = 'fictitious/dotnet/install/dir'; + installer.DotnetCoreInstaller.addToPath(); + const dotnet_root = process.env['DOTNET_ROOT']; + expect(dotnet_root).toBe(process.env['DOTNET_INSTALL_DIR']); + }); + + it(`should export value from DOTNET_INSTALL_DIR env.var to the PATH`, async () => { + process.env['DOTNET_INSTALL_DIR'] = 'fictitious/dotnet/install/dir'; + installer.DotnetCoreInstaller.addToPath(); + const path = process.env['PATH']; + expect(path).toContain(process.env['DOTNET_INSTALL_DIR']); + }); + }); + }); + + describe('DotnetVersionResolver tests', () => { + describe('createDotNetVersion() tests', () => { + each([ + '3.1', + '3.x', + '3.1.x', + '3.1.*', + '3.1.X', + '3.1.2', + '3.1.0-preview1' + ]).test( + 'if valid version is supplied (%s), it should return version object with some value', + async version => { + const dotnetVersionResolver = new installer.DotnetVersionResolver( + version + ); + const versionObject = + await dotnetVersionResolver.createDotNetVersion(); + + expect(!!versionObject.value).toBe(true); + } + ); + + each([ + '.', + '..', + ' . ', + '. ', + ' .', + ' . . ', + ' .. ', + ' . ', + '-1.-1', + '-1', + '-1.-1.-1', + '..3', + '1..3', + '1..', + '.2.3', + '.2.x', + '*.', + '1.2.', + '1.2.-abc', + 'a.b', + 'a.b.c', + 'a.b.c-preview', + ' 0 . 1 . 2 ', + 'invalid' + ]).test( + 'if invalid version is supplied (%s), it should throw', + async version => { + const dotnetVersionResolver = new installer.DotnetVersionResolver( + version + ); + + await expect( + async () => await dotnetVersionResolver.createDotNetVersion() + ).rejects.toThrow(); + } + ); + + each(['3', '3.1', '3.1.x', '3.1.*', '3.1.X']).test( + "if version that can be resolved to 'channel' option is supplied (%s), it should set type to 'channel' in version object", + async version => { + const dotnetVersionResolver = new installer.DotnetVersionResolver( + version + ); + const versionObject = + await dotnetVersionResolver.createDotNetVersion(); + + expect(versionObject.type.toLowerCase().includes('channel')).toBe( + true + ); + } + ); + + each(['6.0', '6.0.x', '6.0.*', '6.0.X']).test( + "if version that can be resolved to 'channel' option is supplied and its major tag is >= 6 (%s), it should set type to 'channel' and qualityFlag to 'true' in version object", + async version => { + const dotnetVersionResolver = new installer.DotnetVersionResolver( + version + ); + const versionObject = + await dotnetVersionResolver.createDotNetVersion(); + + expect(versionObject.type.toLowerCase().includes('channel')).toBe( + true + ); + expect(versionObject.qualityFlag).toBe(true); + } + ); + + each(['3.1.2', '3.1.0-preview1']).test( + "if version that can be resolved to 'version' option is supplied (%s), it should set quality flag to 'false' and type to 'version' in version object", + async version => { + const dotnetVersionResolver = new installer.DotnetVersionResolver( + version + ); + const versionObject = + await dotnetVersionResolver.createDotNetVersion(); + + expect(versionObject.type.toLowerCase().includes('version')).toBe( + true + ); + expect(versionObject.qualityFlag).toBe(false); + } + ); + + each(['3.1.2', '3.1']).test( + 'it should create proper line arguments for powershell/bash installation scripts', + async version => { + const dotnetVersionResolver = new installer.DotnetVersionResolver( + version + ); + const versionObject = + await dotnetVersionResolver.createDotNetVersion(); + const windowsRegEx = new RegExp(/^-(Version|Channel)/); + const nonWindowsRegEx = new RegExp(/^--(version|channel)/); + + if (IS_WINDOWS) { + expect(windowsRegEx.test(versionObject.type)).toBe(true); + expect(nonWindowsRegEx.test(versionObject.type)).toBe(false); + } else { + expect(nonWindowsRegEx.test(versionObject.type)).toBe(true); + expect(windowsRegEx.test(versionObject.type)).toBe(false); + } + } + ); + }); + }); }); - -function normalizeFileContents(contents: string): string { - return contents - .trim() - .replace(new RegExp('\r\n', 'g'), '\n') - .replace(new RegExp('\r', 'g'), '\n'); -} - -async function getDotnet(version: string, quality = ''): Promise { - const dotnetInstaller = new installer.DotnetCoreInstaller( - version, - quality as QualityOptions - ); - const installedVersion = await dotnetInstaller.installDotnet(); - installer.DotnetCoreInstaller.addToPath(); - return installedVersion; -} diff --git a/__tests__/setup-dotnet.test.ts b/__tests__/setup-dotnet.test.ts index 76c1da4..831408c 100644 --- a/__tests__/setup-dotnet.test.ts +++ b/__tests__/setup-dotnet.test.ts @@ -1,114 +1,146 @@ -import * as io from '@actions/io'; import * as core from '@actions/core'; import fs from 'fs'; -import os from 'os'; -import path from 'path'; +import semver from 'semver'; +import * as auth from '../src/authutil'; import * as setup from '../src/setup-dotnet'; -import {IS_WINDOWS} from '../src/utils'; -import {IS_LINUX} from '../src/utils'; - -let toolDir: string; - -if (IS_WINDOWS) { - toolDir = path.join(process.env['PROGRAMFILES'] + '', 'dotnet'); -} else if (IS_LINUX) { - toolDir = '/usr/share/dotnet'; -} else { - toolDir = path.join(process.env['HOME'] + '', '.dotnet'); -} - -function createGlobalJsonPath(dotnetVersion: string) { - const globalJsonPath = path.join(process.cwd(), 'global.json'); - const jsonContents = `{${os.EOL}"sdk": {${os.EOL}"version": "${dotnetVersion}"${os.EOL}}${os.EOL}}`; - if (!fs.existsSync(globalJsonPath)) { - fs.writeFileSync(globalJsonPath, jsonContents); - } - return globalJsonPath; -} - -const tempDir = path.join(__dirname, 'runner', 'temp2'); +import {DotnetCoreInstaller} from '../src/installer'; describe('setup-dotnet tests', () => { - const getInputSpy = jest.spyOn(core, 'getInput'); - const getMultilineInputSpy = jest.spyOn(core, 'getMultilineInput'); - const setOutputSpy = jest.spyOn(core, 'setOutput'); - const inputs = {} as any; - beforeAll(async () => { - process.env.RUNNER_TOOL_CACHE = toolDir; - process.env.DOTNET_INSTALL_DIR = toolDir; - process.env.RUNNER_TEMP = tempDir; - try { - await io.rmRF(`${toolDir}/*`); - await io.rmRF(`${tempDir}/*`); - } catch (err) { - console.log(err.message); - console.log('Failed to remove test directories'); - } - }, 30000); + const getInputSpy = jest.spyOn(core, 'getInput'); + const getMultilineInputSpy = jest.spyOn(core, 'getMultilineInput'); + const setFailedSpy = jest.spyOn(core, 'setFailed'); + const debugSpy = jest.spyOn(core, 'debug'); + const infoSpy = jest.spyOn(core, 'info'); + const setOutputSpy = jest.spyOn(core, 'setOutput'); - afterEach(async () => { - try { - await io.rmRF(path.join(process.cwd(), 'global.json')); - await io.rmRF(`${toolDir}/*`); - await io.rmRF(`${tempDir}/*`); - } catch (err) { - console.log(err.message); - console.log('Failed to remove test directories'); - } - }, 30000); + const existsSyncSpy = jest.spyOn(fs, 'existsSync'); - it('Acquires version of dotnet from global.json if no matching version is installed', async () => { - createGlobalJsonPath('3.1.201'); - await setup.run(); + const maxSatisfyingSpy = jest.spyOn(semver, 'maxSatisfying'); - expect(fs.existsSync(path.join(toolDir, 'sdk', '3.1.201'))).toBe(true); - if (IS_WINDOWS) { - expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); - } else { - expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); - } - }, 400000); + const installDotnetSpy = jest.spyOn( + DotnetCoreInstaller.prototype, + 'installDotnet' + ); + const addToPathSpy = jest.spyOn(DotnetCoreInstaller, 'addToPath'); - it("Sets output with the latest installed by action version if global.json file isn't specified", async () => { - inputs['dotnet-version'] = ['3.1.201', '6.0.401']; + const configAuthenticationSpy = jest.spyOn(auth, 'configAuthentication'); - getMultilineInputSpy.mockImplementation(input => inputs[input]); + describe('run() tests', () => { + beforeEach(() => { + getMultilineInputSpy.mockImplementation(input => inputs[input as string]); + getInputSpy.mockImplementation(input => inputs[input as string]); + }); - await setup.run(); + afterEach(() => { + jest.clearAllMocks(); + jest.resetAllMocks(); + }); - expect(setOutputSpy).toHaveBeenCalledWith('dotnet-version', '6.0.401'); - }, 400000); + it('should fail the action if global-json-file input is present, but the file does not exist in the file system', async () => { + inputs['global-json-file'] = 'fictitious.json'; + inputs['dotnet-version'] = []; - it("Sets output with the version specified in global.json, if it's present", async () => { - createGlobalJsonPath('3.0.103'); + const expectedErrorMessage = `The specified global.json file '${inputs['global-json-file']}' does not exist`; - inputs['dotnet-version'] = ['3.1.201', '6.0.401']; - inputs['global-json-file'] = './global.json'; + await setup.run(); + expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage); + }); - getMultilineInputSpy.mockImplementation(input => inputs[input]); + test(`if 'dotnet-version' and 'global-json-file' inputs aren't present, should log into debug output, try to find global.json in the repo root, fail and log message into info output`, async () => { + inputs['global-json-file'] = ''; + inputs['dotnet-version'] = []; - getInputSpy.mockImplementation(input => inputs[input]); + maxSatisfyingSpy.mockImplementation(() => null); + setOutputSpy.mockImplementation(() => {}); - await setup.run(); + const expectedDebugMessage = + 'No version found, trying to find version from global.json'; + const expectedInfoMessage = `global.json wasn't found in the root directory. No .NET version will be installed.`; - expect(setOutputSpy).toHaveBeenCalledWith('dotnet-version', '3.0.103'); - }, 400000); + await setup.run(); - it('Sets output with the version specified in global.json with absolute path', async () => { - const globalJsonPath = createGlobalJsonPath('3.0.103'); + expect(debugSpy).toHaveBeenCalledWith(expectedDebugMessage); + expect(existsSyncSpy).toHaveBeenCalled(); + expect(infoSpy).toHaveBeenCalledWith(expectedInfoMessage); + }); - inputs['dotnet-version'] = ['3.1.201', '6.0.401']; - inputs['global-json-file'] = globalJsonPath; + it('should fail the action if quality is supplied but its value is not supported', async () => { + inputs['global-json-file'] = ''; + inputs['dotnet-version'] = ['6.0']; + inputs['dotnet-quality'] = 'fictitiousQuality'; - getMultilineInputSpy.mockImplementation(input => inputs[input]); + const expectedErrorMessage = `${inputs['dotnet-quality']} is not a supported value for 'dotnet-quality' option. Supported values are: daily, signed, validated, preview, ga.`; - getInputSpy.mockImplementation(input => inputs[input]); + await setup.run(); + expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage); + }); - await setup.run(); + it('should call installDotnet() multiple times if dotnet-version multiline input is provided', async () => { + inputs['global-json-file'] = ''; + inputs['dotnet-version'] = ['6.0', '7.0']; + inputs['dotnet-quality'] = ''; - expect(setOutputSpy).toHaveBeenCalledWith('dotnet-version', '3.0.103'); - }, 400000); + installDotnetSpy.mockImplementation(() => Promise.resolve('')); + + await setup.run(); + expect(installDotnetSpy).toHaveBeenCalledTimes(2); + }); + + it('should call addToPath() after installation complete', async () => { + inputs['global-json-file'] = ''; + inputs['dotnet-version'] = ['6.0', '7.0']; + inputs['dotnet-quality'] = ''; + + installDotnetSpy.mockImplementation(() => Promise.resolve('')); + addToPathSpy.mockImplementation(() => {}); + + await setup.run(); + expect(addToPathSpy).toHaveBeenCalledTimes(1); + }); + + it('should call auth.configAuthentication() if source-url input is provided', async () => { + inputs['global-json-file'] = ''; + inputs['dotnet-version'] = []; + inputs['dotnet-quality'] = ''; + inputs['source-url'] = 'fictitious.source.url'; + + configAuthenticationSpy.mockImplementation(() => {}); + + await setup.run(); + expect(configAuthenticationSpy).toHaveBeenCalledWith( + inputs['source-url'], + undefined + ); + }); + + it('should call auth.configAuthentication() with proper parameters if source-url and config-file inputs are provided', async () => { + inputs['global-json-file'] = ''; + inputs['dotnet-version'] = []; + inputs['dotnet-quality'] = ''; + inputs['source-url'] = 'fictitious.source.url'; + inputs['config-file'] = 'fictitious.path'; + + configAuthenticationSpy.mockImplementation(() => {}); + setOutputSpy.mockImplementation(() => {}); + + await setup.run(); + expect(configAuthenticationSpy).toHaveBeenCalledWith( + inputs['source-url'], + inputs['config-file'] + ); + }); + + it('should call setOutput() after installation complete', async () => { + inputs['dotnet-version'] = ['6.0.300']; + + installDotnetSpy.mockImplementation(() => Promise.resolve('')); + addToPathSpy.mockImplementation(() => {}); + + await setup.run(); + expect(setOutputSpy).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/externals/install-dotnet.ps1 b/externals/install-dotnet.ps1 index b0b131a..c9a30cb 100644 --- a/externals/install-dotnet.ps1 +++ b/externals/install-dotnet.ps1 @@ -9,6 +9,12 @@ .DESCRIPTION Installs dotnet cli. If dotnet installation already exists in the given directory it will update it only if the requested version differs from the one already installed. + + Note that the intended use of this script is for Continuous Integration (CI) scenarios, where: + - The SDK needs to be installed without user interaction and without admin rights. + - The SDK installation doesn't need to persist across multiple CI runs. + To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer. + .PARAMETER Channel Default: LTS Download from the Channel specified. Possible values: @@ -164,6 +170,12 @@ function Say-Verbose($str) { } } +function Measure-Action($name, $block) { + $time = Measure-Command $block + $totalSeconds = $time.TotalSeconds + Say-Verbose "⏱ Action '$name' took $totalSeconds seconds" +} + function Say-Invocation($Invocation) { $command = $Invocation.MyCommand; $args = (($Invocation.BoundParameters.Keys | foreach { "-$_ `"$($Invocation.BoundParameters[$_])`"" }) -join " ") @@ -1104,10 +1116,10 @@ function Prepare-Install-Directory { } } -Say "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" -Say "- The SDK needs to be installed without user interaction and without admin rights." -Say "- The SDK installation doesn't need to persist across multiple CI runs." -Say "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.`r`n" +Say-Verbose "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" +Say-Verbose "- The SDK needs to be installed without user interaction and without admin rights." +Say-Verbose "- The SDK installation doesn't need to persist across multiple CI runs." +Say-Verbose "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.`r`n" if ($SharedRuntime -and (-not $Runtime)) { $Runtime = "dotnet" @@ -1115,14 +1127,16 @@ if ($SharedRuntime -and (-not $Runtime)) { $OverrideNonVersionedFiles = !$SkipNonVersionedFiles -$CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture -$NormalizedQuality = Get-NormalizedQuality $Quality -Say-Verbose "Normalized quality: '$NormalizedQuality'" -$NormalizedChannel = Get-NormalizedChannel $Channel -Say-Verbose "Normalized channel: '$NormalizedChannel'" -$NormalizedProduct = Get-NormalizedProduct $Runtime -Say-Verbose "Normalized product: '$NormalizedProduct'" -$FeedCredential = ValidateFeedCredential $FeedCredential +Measure-Action "Product discovery" { + $script:CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture + $script:NormalizedQuality = Get-NormalizedQuality $Quality + Say-Verbose "Normalized quality: '$NormalizedQuality'" + $script:NormalizedChannel = Get-NormalizedChannel $Channel + Say-Verbose "Normalized channel: '$NormalizedChannel'" + $script:NormalizedProduct = Get-NormalizedProduct $Runtime + Say-Verbose "Normalized product: '$NormalizedProduct'" + $script:FeedCredential = ValidateFeedCredential $FeedCredential +} $InstallRoot = Resolve-Installation-Path $InstallDir Say-Verbose "InstallRoot: $InstallRoot" @@ -1200,7 +1214,7 @@ if ($DryRun) { return } -Prepare-Install-Directory +Measure-Action "Installation directory preparation" { Prepare-Install-Directory } $ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) Say-Verbose "Zip path: $ZipPath" @@ -1214,7 +1228,7 @@ foreach ($link in $DownloadLinks) Say-Verbose "Downloading `"$($link.type)`" link $($link.downloadLink)" try { - DownloadFile -Source $link.downloadLink -OutPath $ZipPath + Measure-Action "Package download" { DownloadFile -Source $link.downloadLink -OutPath $ZipPath } Say-Verbose "Download succeeded." $DownloadSucceeded = $true $DownloadedLink = $link @@ -1251,7 +1265,7 @@ if (-not $DownloadSucceeded) { } Say "Extracting the archive." -Extract-Dotnet-Package -ZipPath $ZipPath -OutPath $InstallRoot +Measure-Action "Package extraction" { Extract-Dotnet-Package -ZipPath $ZipPath -OutPath $InstallRoot } # Check if the SDK version is installed; if not, fail the installation. $isAssetInstalled = $false @@ -1277,225 +1291,224 @@ if (!$isAssetInstalled) { SafeRemoveFile -Path $ZipPath -Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot +Measure-Action "Setting up shell environment" { Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot } Say "Note that the script does not resolve dependencies during installation." Say "To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install/windows#dependencies" Say "Installed version is $($DownloadedLink.effectiveVersion)" Say "Installation finished" - # SIG # Begin signature block -# MIInzgYJKoZIhvcNAQcCoIInvzCCJ7sCAQExDzANBglghkgBZQMEAgEFADB5Bgor +# MIInvwYJKoZIhvcNAQcCoIInsDCCJ6wCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB7pzZ0nuEMd30h -# n1EcAYUQN+1clltqaLf9611TDrw/laCCDYUwggYDMIID66ADAgECAhMzAAACzfNk -# v/jUTF1RAAAAAALNMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBhfTi3SRn7+vyy +# uCXKPjhiawegWZ493EcaOEycbgkZcKCCDXYwggX0MIID3KADAgECAhMzAAACy7d1 +# OfsCcUI2AAAAAALLMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NjAyWhcNMjMwNTExMjA0NjAyWjB0MQsw +# bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NTU5WhcNMjMwNTExMjA0NTU5WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDrIzsY62MmKrzergm7Ucnu+DuSHdgzRZVCIGi9CalFrhwtiK+3FIDzlOYbs/zz -# HwuLC3hir55wVgHoaC4liQwQ60wVyR17EZPa4BQ28C5ARlxqftdp3H8RrXWbVyvQ -# aUnBQVZM73XDyGV1oUPZGHGWtgdqtBUd60VjnFPICSf8pnFiit6hvSxH5IVWI0iO -# nfqdXYoPWUtVUMmVqW1yBX0NtbQlSHIU6hlPvo9/uqKvkjFUFA2LbC9AWQbJmH+1 -# uM0l4nDSKfCqccvdI5l3zjEk9yUSUmh1IQhDFn+5SL2JmnCF0jZEZ4f5HE7ykDP+ -# oiA3Q+fhKCseg+0aEHi+DRPZAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU0WymH4CP7s1+yQktEwbcLQuR9Zww -# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh -# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ3MDUzMDAfBgNVHSMEGDAW -# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v -# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw -# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov -# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx -# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB -# AE7LSuuNObCBWYuttxJAgilXJ92GpyV/fTiyXHZ/9LbzXs/MfKnPwRydlmA2ak0r -# GWLDFh89zAWHFI8t9JLwpd/VRoVE3+WyzTIskdbBnHbf1yjo/+0tpHlnroFJdcDS -# MIsH+T7z3ClY+6WnjSTetpg1Y/pLOLXZpZjYeXQiFwo9G5lzUcSd8YVQNPQAGICl -# 2JRSaCNlzAdIFCF5PNKoXbJtEqDcPZ8oDrM9KdO7TqUE5VqeBe6DggY1sZYnQD+/ -# LWlz5D0wCriNgGQ/TWWexMwwnEqlIwfkIcNFxo0QND/6Ya9DTAUykk2SKGSPt0kL -# tHxNEn2GJvcNtfohVY/b0tuyF05eXE3cdtYZbeGoU1xQixPZAlTdtLmeFNly82uB -# VbybAZ4Ut18F//UrugVQ9UUdK1uYmc+2SdRQQCccKwXGOuYgZ1ULW2u5PyfWxzo4 -# BR++53OB/tZXQpz4OkgBZeqs9YaYLFfKRlQHVtmQghFHzB5v/WFonxDVlvPxy2go -# a0u9Z+ZlIpvooZRvm6OtXxdAjMBcWBAsnBRr/Oj5s356EDdf2l/sLwLFYE61t+ME -# iNYdy0pXL6gN3DxTVf2qjJxXFkFfjjTisndudHsguEMk8mEtnvwo9fOSKT6oRHhM -# 9sZ4HTg/TTMjUljmN3mBYWAWI5ExdC1inuog0xrKmOWVMIIHejCCBWKgAwIBAgIK -# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV +# AQC3sN0WcdGpGXPZIb5iNfFB0xZ8rnJvYnxD6Uf2BHXglpbTEfoe+mO//oLWkRxA +# wppditsSVOD0oglKbtnh9Wp2DARLcxbGaW4YanOWSB1LyLRpHnnQ5POlh2U5trg4 +# 3gQjvlNZlQB3lL+zrPtbNvMA7E0Wkmo+Z6YFnsf7aek+KGzaGboAeFO4uKZjQXY5 +# RmMzE70Bwaz7hvA05jDURdRKH0i/1yK96TDuP7JyRFLOvA3UXNWz00R9w7ppMDcN +# lXtrmbPigv3xE9FfpfmJRtiOZQKd73K72Wujmj6/Su3+DBTpOq7NgdntW2lJfX3X +# a6oe4F9Pk9xRhkwHsk7Ju9E/AgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUrg/nt/gj+BBLd1jZWYhok7v5/w4w +# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW +# MBQGA1UEBRMNMjMwMDEyKzQ3MDUyODAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci +# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG +# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu +# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 +# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAJL5t6pVjIRlQ8j4dAFJ +# ZnMke3rRHeQDOPFxswM47HRvgQa2E1jea2aYiMk1WmdqWnYw1bal4IzRlSVf4czf +# zx2vjOIOiaGllW2ByHkfKApngOzJmAQ8F15xSHPRvNMmvpC3PFLvKMf3y5SyPJxh +# 922TTq0q5epJv1SgZDWlUlHL/Ex1nX8kzBRhHvc6D6F5la+oAO4A3o/ZC05OOgm4 +# EJxZP9MqUi5iid2dw4Jg/HvtDpCcLj1GLIhCDaebKegajCJlMhhxnDXrGFLJfX8j +# 7k7LUvrZDsQniJZ3D66K+3SZTLhvwK7dMGVFuUUJUfDifrlCTjKG9mxsPDllfyck +# 4zGnRZv8Jw9RgE1zAghnU14L0vVUNOzi/4bE7wIsiRyIcCcVoXRneBA3n/frLXvd +# jDsbb2lpGu78+s1zbO5N0bhHWq4j5WMutrspBxEhqG2PSBjC5Ypi+jhtfu3+x76N +# mBvsyKuxx9+Hm/ALnlzKxr4KyMR3/z4IRMzA1QyppNk65Ui+jB14g+w4vole33M1 +# pVqVckrmSebUkmjnCshCiH12IFgHZF7gRwE4YZrJ7QjxZeoZqHaKsQLRMp653beB +# fHfeva9zJPhBSdVcCW7x9q0c2HVPLJHX9YCUU714I+qtLpDGrdbZxD9mikPqL/To +# /1lDZ0ch8FtePhME7houuoPcMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq +# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x +# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv +# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG +# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG +# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg +# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 +# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr +# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg +# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy +# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 +# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh +# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k +# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB +# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn +# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 +# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w +# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o +# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD +# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa +# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny +# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG +# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t +# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV +# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 +# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG +# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl +# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb +# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l +# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 +# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 +# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 +# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam +# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa +# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah +# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA +# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt +# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr +# /Xmfwb1tbWrJUnMTDXpQzTGCGZ8wghmbAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw +# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN +# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp +# Z25pbmcgUENBIDIwMTECEzMAAALLt3U5+wJxQjYAAAAAAsswDQYJYIZIAWUDBAIB +# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO +# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFmuaTXYQ37AFvsEol24fdW+ +# nRqHcc1fr+VQVdqhXc/vMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A +# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB +# BQAEggEAjY5XW5Ly7TJ1OTbeIR98xU+2dmtw7L71ws+ICnQCGhj2xJDUK+5yrTfO +# 8C98l/P4ynFi33Dl8z2YElqUCuqEXbiCzz06lIL4NuibC5DV/X80ZmICR/NYd2v1 +# ww7IH+7dpsHAowBBindCYpVwQ3Ea3kDWgsjPAinAysFFushSOnNWFvrF6vi2smrs +# smbrAAhEhSfLd1Pxxdw73hQ0YjM/D3F3opaybMQ0blpHhOaqtbiyYzvk0doIzBEc +# trSH4NDIc3yLNj5VbjSczpexE+hyQNY4xCtwco4bVtXhONUihv08AIKR8+sIaI7A +# mM/SWrrwGYSSSxydKqDei7biKG4jDqGCFykwghclBgorBgEEAYI3AwMBMYIXFTCC +# FxEGCSqGSIb3DQEHAqCCFwIwghb+AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq +# hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl +# AwQCAQUABCB6Hzt2gUb/WZK8fvVnOocriE4rYr6mscZi3gZnBCpiigIGZBr2iMZU +# GBMyMDIzMDMzMTE1MjEwNi41MTZaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV +# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE +# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl +# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO +# OjA4NDItNEJFNi1DMjlBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT +# ZXJ2aWNloIIReDCCBycwggUPoAMCAQICEzMAAAGybkADf26plJIAAQAAAbIwDQYJ +# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x +# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv +# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjIw +# OTIwMjAyMjAxWhcNMjMxMjE0MjAyMjAxWjCB0jELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv -# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm -# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw -# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE -# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD -# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG -# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la -# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc -# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D -# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ -# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk -# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 -# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd -# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL -# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd -# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 -# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS -# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI -# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL -# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD -# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv -# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf -# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 -# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf -# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF -# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h -# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA -# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn -# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 -# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b -# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ -# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy -# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp -# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi -# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb -# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS -# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL -# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX -# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGZ8wghmbAgEBMIGVMH4x -# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt -# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p -# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAALN82S/+NRMXVEAAAAA -# As0wDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw -# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEINK7 -# cJe0KVfbcXchjID30U/cUg7pWAQUa3+n8JuhjLCLMEIGCisGAQQBgjcCAQwxNDAy -# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20wDQYJKoZIhvcNAQEBBQAEggEAODLxcflOtjpIXXIhbYyQ0wFeBx0NrmoMU/Ri -# e7CRrAieAbG4iLJzs4DhUov5iuMHY6AAbLWAH54QlSkd4XNp6POsE7lSzN9yjlVw -# V/e0XCaYeXIbtd75hGd5P7wAhM4m2ViDI4IRHyQtjysW0U0F6YiqNlFm7Fzo5Si6 -# l2XxvuEDSdyJcEN70wHQajx6bKfnI/oMY59iGjDXvDP/6cQV9NI0gPHFTwPKA7vg -# PySyVFEG7dQMoEwAWy9GHbcS//RulgUwBhWcrtUP411XLSO6is2VTknwbdglc9HZ -# zViuS4C1ujHlPrlMzm8Y5iGVIQCna5w2NU/kGsSK5+dMkovomKGCFykwghclBgor -# BgEEAYI3AwMBMYIXFTCCFxEGCSqGSIb3DQEHAqCCFwIwghb+AgEDMQ8wDQYJYIZI -# AWUDBAIBBQAwggFZBgsqhkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGE -# WQoDATAxMA0GCWCGSAFlAwQCAQUABCDRz6ce9oWlH6+o0BtjmAjtvEMN1hfhIA5v -# +wTZHvB4XgIGY2PeyIloGBMyMDIyMTExMDE1MDUxNi43MzRaMASAAgH0oIHYpIHV -# MIHSMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH -# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQL -# EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsT -# HVRoYWxlcyBUU1MgRVNOOkEyNDAtNEI4Mi0xMzBFMSUwIwYDVQQDExxNaWNyb3Nv -# ZnQgVGltZS1TdGFtcCBTZXJ2aWNloIIReDCCBycwggUPoAMCAQICEzMAAAG4CNTB -# uHngUUkAAQAAAbgwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNV -# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv -# c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg -# UENBIDIwMTAwHhcNMjIwOTIwMjAyMjE2WhcNMjMxMjE0MjAyMjE2WjCB0jELMAkG -# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx -# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9z -# b2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMg -# VFNTIEVTTjpBMjQwLTRCODItMTMwRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt -# U3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJwb -# sfwRHERn5C95QPGn37tJ5vOiY9aWjeIDxpgaXaYGiqsw0G0cvCK3YulrqemEf2Ck -# GSdcOJAF++EqhOSqrO13nGcjqw6hFNnsGwKANyzddwnOO0jz1lfBIIu77TbfNvna -# WbwSRu0DTGHA7n7PR0MYJ9bC/HopStpbFf606LKcTWnwaUuEdAhx6FAqg1rkgugi -# uuaaxKyxRkdjFZLKFXEXL9p01PtwS0fG6vZiRVnEKgeal2TeLvdAIqapBwltPYif -# gqnp7Z4VJMcPo0TWmRNVFOcHRNwWHehN9xg6ugIGXPo7hMpWrPgg4moHO2epc0T3 -# 6rgm9hlDrl28bG5TakmV7NJ98kbF5lgtlrowT6ecwEVtuLd4a0gzYqhanW7zaFZn -# Dft5yMexy59ifETdzpwArj2nJAyIsiq1PY3XPm2mUMLlACksqelHKfWihK/Fehw/ -# mziovBVwkkr/G0F19OWgR+MBUKifwpOyQiLAxrqvVnfCY4QjJCZiHIuS15HCQ/TI -# t/Qj4x1WvRa1UqjnmpLu4/yBYWZsdvZoq8SXI7iOs7muecAJeEkYlM6iOkMighzE -# hjQK9ThPpoAtluXbL7qIHGrfFlHmX/4soc7jj1j8uB31U34gJlB2XphjMaT+E+O9 -# SImk/6GRV9Sm8C88Fnmm2VdwMluCNAUzPFjfvHx3AgMBAAGjggFJMIIBRTAdBgNV -# HQ4EFgQUxP1HJTeFwzNYo1njfucXuUfQaW4wHwYDVR0jBBgwFoAUn6cVXQBeYl2D -# 9OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3Nv -# ZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUy -# MDIwMTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDov -# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1l -# LVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUB -# Af8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQAD -# ggIBAJ9uk8miwpMoKw3D996piEzbegAGxkABHYn2vP2hbqnkS9U97s/6QlyZOhGF -# sVudaiLeRZZTsaG5hR0oCuBINZ/lelo5xzHc+mBOpBXpxSaW1hqoxaCLsVH1EBtz -# 7in25Hjy+ejuBcilH6EZ0ZtNxmWGIQz8R0AuS0Tj4VgJXHIlXP9dVOiyGo9Velrk -# +FGx/BC+iEuCaKd/IsypHPiCUCh52DGc91s2S7ldQx1H4CljOAtanDfbvSejASWL -# o/s3w0XMAbDurWNns0XidAF2RnL1PaxoOyz9VYakNGK4F3/uJRZnVgbsCYuwNX1B -# mSwM1ZbPSnggNSGTZx/FQ20Jj/ulrK0ryAbvNbNb4kkaS4a767ifCqvUOFLlUT8P -# N43hhldxI6yHPMOWItJpEHIZBiTNKblBsYbIrghb1Ym9tfSsLa5ZJDzVZNndRfhU -# qJOyXF+CVm9OtVmFDG9kIwM6QAX8Q0if721z4VOzZNvD8ktg1lI+XjXgXDJVs3h4 -# 7sMu9GXSYzky+7dtgmc3iRPkda3YVRdmPJtNFN0NLybcssE7vhFCij75eDGQBFq0 -# A4KVG6uBdr6UTWwE0VKHxBz2BpGvn7BCs+5yxnF+HV6CUickDqqPi/II7Zssd9Eb -# P9uzj4luldXDAPrWGtdGq+wK0odlGNVuCMxsL3hn8+KiO9UiMIIHcTCCBVmgAwIB -# AgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UE -# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc -# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0 -# IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1 -# WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu -# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv -# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCC -# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O -# 1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZn -# hUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t -# 1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxq -# D89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmP -# frVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSW -# rAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv -# 231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zb -# r17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYcten -# IPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQc -# xWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17a -# j54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQAB -# MCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQU -# n6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEw -# QTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9E -# b2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQB -# gjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/ -# MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJ -# oEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01p -# Y1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYB -# BQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9v -# Q2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3h -# LB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x -# 5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74p -# y27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1A -# oL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbC -# HcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB -# 9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNt -# yo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3 -# rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcV -# v7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A24 -# 5oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lw -# Y1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB -# 0jELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl -# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMk -# TWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1U -# aGFsZXMgVFNTIEVTTjpBMjQwLTRCODItMTMwRTElMCMGA1UEAxMcTWljcm9zb2Z0 -# IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAcGteVqFx/IbTKXHL -# euXCPRPMD7uggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu -# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv -# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN -# BgkqhkiG9w0BAQUFAAIFAOcW7qowIhgPMjAyMjExMTAxMTI5NDZaGA8yMDIyMTEx -# MTExMjk0NlowdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA5xbuqgIBADAHAgEAAgIE -# qTAHAgEAAgIRVjAKAgUA5xhAKgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEE -# AYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GB -# AGsU2HTQg158bHX+QngoY7NVfCbGRaLjQOi8geKi26qQWAxll9QLFg4+epiG2nZB -# eQvhxeNmIzounhWfJ+gfhFMi8aBT5z4dLK9iBtmpG1Y14RmSS4andiUlS6bVNVNe -# WGObqHijMVeMOphiTaAfzR6zSASDaG0CfVm9bNBOnZZsMYIEDTCCBAkCAQEwgZMw -# fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl -# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd -# TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAG4CNTBuHngUUkAAQAA -# AbgwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRAB -# BDAvBgkqhkiG9w0BCQQxIgQg578XwPrBwneU95xu1sHFncHeCC0UPQ7QK7PvSSby -# VpwwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCAo69Y4oHA7Q4pS+Y1NsBfr -# pIYTeWsPeGTami0X0PD7HzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI -# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv -# ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD -# QSAyMDEwAhMzAAABuAjUwbh54FFJAAEAAAG4MCIEIJVlMK4mQfdJaAZx7Unfka/I -# D4Wbw5edh/SR7TTptzRqMA0GCSqGSIb3DQEBCwUABIICAA1QlR3ywR7e+jqZ++NC -# xsIwREiwVS70CEkbH8XpPRS0mFS0SHcCfpwymGfdep3D0CWk0PIfMhXq0SD97iBI -# rOLdHglVBkMYTjGEBHyBzv/LevAZUuzoc5aqyIF4Ywa5KS4PGbMSuRK5CKAojOzH -# A/vp2/KYuADmf9kOOgOfDVicyfoqZ+3W+QaUI/k0KKV4dPLF55+y18C+Td6sR60Y -# AkcvGZObuj/OkREhdTJ1qJ2E/4RKG8gtGY1DfluLon7+UvS/ciWDWrJnHMmkxM11 -# cYuRIvLArIdq/YS2bcSnY6AVO2zYjj7gCqDN9GuCurstUKC5uxVl3VNxntC0u3Le -# BoI/R5uMYlTXodW8ukLNL6zHrQ4wI4udgW77KJref+3E1PEpZBRMxwose7Vt8lDc -# sW1vdM+eZzUXRLhDR8a0Nai7+PaNoukoGf4pvwsu8Mkeji5a0hWtU9lUVRv6nzue -# 3L2olhsbiHhAET7N6Rj0kzEhbUgfVUJrGvNlWOfN7MDr+OpArGXMPLtovbKTLtXF -# v/GrJo9wQuyqUmY6KQSRDZgOw1CcoZpJcy40HG/aOlJwk03N13OZD5H3KfHwEphR -# YnbGwGq9zUId5druSr5s40Yyl3idAkqmI5SXAm9v/gRq2X9vMU0a7KqXet9wO62F -# TqxV+7Qp48Vw6hW1g+Q7oWoc +# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl +# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowODQyLTRC +# RTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC +# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMqiZTIde/lQ4rC+Bml5f/Wu +# q/xKTxrfbG23HofmQ+qZAN4GyO73PF3y9OAfpt7Qf2jcldWOGUB+HzBuwllYyP3f +# x4MY8zvuAuB37FvoytnNC2DKnVrVlHOVcGUL9CnmhDNMA2/nskjIf2IoiG9J0qLY +# r8duvHdQJ9Li2Pq9guySb9mvUL60ogslCO9gkh6FiEDwMrwUr8Wja6jFpUTny8tg +# 0N0cnCN2w4fKkp5qZcbUYFYicLSb/6A7pHCtX6xnjqwhmJoib3vkKJyVxbuFLRhV +# XxH95b0LHeNhifn3jvo2j+/4QV10jEpXVW+iC9BsTtR69xvTjU51ZgP7BR4YDEWq +# 7JsylSOv5B5THTDXRf184URzFhTyb8OZQKY7mqMh7c8J8w1sEM4XDUF2UZNy829N +# VCzG2tfdEXZaHxF8RmxpQYBxyhZwY1rotuIS+gfN2eq+hkAT3ipGn8/KmDwDtzAb +# nfuXjApgeZqwgcYJ8pDJ+y/xU6ouzJz1Bve5TTihkiA7wQsQe6R60Zk9dPdNzw0M +# K5niRzuQZAt4GI96FhjhlUWcUZOCkv/JXM/OGu/rgSplYwdmPLzzfDtXyuy/GCU5 +# I4l08g6iifXypMgoYkkceOAAz4vx1x0BOnZWfI3fSwqNUvoN7ncTT+MB4Vpvf1QB +# ppjBAQUuvui6eCG0MCVNAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUmfIngFzZEZlP +# kjDOVluBSDDaanEwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD +# VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j +# cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG +# CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu +# Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw +# MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD +# CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBANxHtu3FzIabaDbW +# qswdKBlAhKXRCN+5CSMiv2TYa4i2QuWIm+99piwAhDhADfbqor1zyLi95Y6GQnvI +# WUgdeC7oL1ZtZye92zYK+EIfwYZmhS+CH4infAzUvscHZF3wlrJUfPUIDGVP0lCY +# Vse9mguvG0dqkY4ayQPEHOvJubgZZaOdg/N8dInd6fGeOc+0DoGzB+LieObJ2Q0A +# tEt3XN3iX8Cp6+dZTX8xwE/LvhRwPpb/+nKshO7TVuvenwdTwqB/LT6CNPaElwFe +# KxKrqRTPMbHeg+i+KnBLfwmhEXsMg2s1QX7JIxfvT96md0eiMjiMEO22LbOzmLMN +# d3LINowAnRBAJtX+3/e390B9sMGMHp+a1V+hgs62AopBl0p/00li30DN5wEQ5If3 +# 5Zk7b/T6pEx6rJUDYCti7zCbikjKTanBnOc99zGMlej5X+fC/k5ExUCrOs3/VzGR +# CZt5LvVQSdWqq/QMzTEmim4sbzASK9imEkjNtZZyvC1CsUcD1voFktld4mKMjE+u +# DEV3IddD+DrRk94nVzNPSuZXewfVOnXHSeqG7xM3V7fl2aL4v1OhL2+JwO1Tx3B0 +# irO1O9qbNdJk355bntd1RSVKgM22KFBHnoL7Js7pRhBiaKmVTQGoOb+j1Qa7q+ci +# xGo48Vh9k35BDsJS/DLoXFSPDl4mMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ +# mQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m +# dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh +# dGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1 +# WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD +# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEB +# BQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjK +# NVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhg +# fWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJp +# rx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/d +# vI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka9 +# 7aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKR +# Hh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9itu +# qBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyO +# ArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItb +# oKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6 +# bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6t +# AgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQW +# BBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacb +# UzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYz +# aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnku +# aHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIA +# QwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2 +# VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwu +# bWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEw +# LTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93 +# d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt +# MjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/q +# XBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6 +# U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVt +# I1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis +# 9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTp +# kbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0 +# sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138e +# W0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJ +# sWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7 +# Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0 +# dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQ +# tB1VM1izoXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMx +# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT +# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh +# bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjow +# ODQyLTRCRTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy +# dmljZaIjCgEBMAcGBSsOAwIaAxUAjhJ+EeySRfn2KCNsjn9cF9AUSTqggYMwgYCk +# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD +# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF +# AOfRUdUwIhgPMjAyMzAzMzEyMDM0MjlaGA8yMDIzMDQwMTIwMzQyOVowdDA6Bgor +# BgEEAYRZCgQBMSwwKjAKAgUA59FR1QIBADAHAgEAAgIKJDAHAgEAAgIRLzAKAgUA +# 59KjVQIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID +# B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAJlOESCa/uRR1x6GunE8 +# K/WgHWTpSE31EITDOfTMvDcF4ptngCS5aOc4gfzmhNNehWfP6EOrgoSQzJYZ4YCh +# fYbHNMk56f18sq8t7y2hgR7KixcEo/4HVzeSdaOclHNc4Gn7kCGpMvpT3Xz9Lzc7 +# UKWDZ0zkNKnbS8TZLNueVQwfMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMx +# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT +# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt +# U3RhbXAgUENBIDIwMTACEzMAAAGybkADf26plJIAAQAAAbIwDQYJYIZIAWUDBAIB +# BQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQx +# IgQgXhJRuHCXk3arJvifIY3DBe9Ce9EmlP1y6U4XkgL31DkwgfoGCyqGSIb3DQEJ +# EAIvMYHqMIHnMIHkMIG9BCBTeM485+E+t4PEVieUoFKX7PVyLo/nzu+htJPCG04+ +# NTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw +# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x +# JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABsm5A +# A39uqZSSAAEAAAGyMCIEIGGWlnNnYHrB5HguWG0/nJd/WvSrCogze+QCpenu3IM5 +# MA0GCSqGSIb3DQEBCwUABIICADVOLTuNxeEnBOfZpb7Nv4uf91W/Ho5i99zenDSJ +# x5QHVs+bKXmgc3a7/SSsliAT3zygHc7cH4zARbCZePLTivByKmeG08Ka35eyR+FK +# awSNrI/X+eVIC6nw/egCwviBC1NAG8jHGkuScbHeiiGajvS6lp3ORML7UexMuE4w +# 9SEumoghljCLZMwCSvw+3WxhQoBEZroR8u+PID2RdD0vi85FjKPWcZZijVLqHeFi +# TnuFqwRCLTV0MV+dDCbjwXneIqV+AVlnqb9iDMr3ZhISlRcy9XJNpY5vQBj/wqUW +# vefrmpdz0LNkdtXYThPkyl3mha2KsoQi5SA9zSjlAjFgY3ppmXvi3Frbfqk+iL+f +# l/Qc4+B71jG4t28lTWKteJiHqo+6AUXK2rlAl0d74yvhO6N8lMMtXhdJc8JABYn1 +# v2/KKZn5RvPFF8QP7Ac1saIe1+gUFNcsYOLaMm/xl8E6kefWwZnm5Rhm606g1AC/ +# N5Wo08aAs0ymTPH91dEbmOURXLbA3vCyG7kbfgnhCs/j7oQHWaFDzEYuXDIA4ICT +# dxPUTltbq3OWdp0PAS8JSEKPQFaOoQEnPa4adrXWxMvOmel8IGqJiQ+BPOaLQG64 +# Qu2tMkH/5szb1fsEnCe8SJmy5ESF+kmpnLBtJ17Y9o+9nJHF5ddFmvzy+LUaIqDN +# cOfH # SIG # End signature block diff --git a/externals/install-dotnet.sh b/externals/install-dotnet.sh index ecb2b6c..bdaa67e 100755 --- a/externals/install-dotnet.sh +++ b/externals/install-dotnet.sh @@ -1617,6 +1617,10 @@ do echo " $script_name -h|-?|--help" echo "" echo "$script_name is a simple command line interface for obtaining dotnet cli." + echo " Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" + echo " - The SDK needs to be installed without user interaction and without admin rights." + echo " - The SDK installation doesn't need to persist across multiple CI runs." + echo " To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer." echo "" echo "Options:" echo " -c,--channel Download from the channel specified, Defaults to \`$channel\`." @@ -1694,10 +1698,10 @@ do shift done -say "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" -say "- The SDK needs to be installed without user interaction and without admin rights." -say "- The SDK installation doesn't need to persist across multiple CI runs." -say "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.\n" +say_verbose "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" +say_verbose "- The SDK needs to be installed without user interaction and without admin rights." +say_verbose "- The SDK installation doesn't need to persist across multiple CI runs." +say_verbose "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.\n" if [ "$internal" = true ] && [ -z "$(echo $feed_credential)" ]; then message="Provide credentials via --feed-credential parameter." @@ -1731,4 +1735,4 @@ fi say "Note that the script does not resolve dependencies during installation." say "To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the \"Dependencies\" section." -say "Installation finished successfully." +say "Installation finished successfully." \ No newline at end of file From e8501859aa05b6b71ff73e66a11c30973f9e01ff Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Mon, 15 May 2023 14:24:28 +0200 Subject: [PATCH 23/32] Update unit tests --- __tests__/installer.test.ts | 51 +++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 4694e5d..0d264b5 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -48,8 +48,13 @@ describe('installer tests', () => { it('should return version of .NET SDK after installation complete', async () => { const inputVersion = '3.1.100'; const inputQuality = '' as QualityOptions; + const stdout = `Fictitious dotnet version ${inputVersion} is installed`; getExecOutputSpy.mockImplementation(() => { - return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + return Promise.resolve({ + exitCode: 0, + stdout: `${stdout}`, + stderr: '' + }); }); maxSatisfyingSpy.mockImplementation(() => inputVersion); @@ -65,9 +70,14 @@ describe('installer tests', () => { it(`should supply 'version' argument to the installation script if supplied version is in A.B.C syntax`, async () => { const inputVersion = '6.0.300'; const inputQuality = '' as QualityOptions; + const stdout = `Fictitious dotnet version ${inputVersion} is installed`; getExecOutputSpy.mockImplementation(() => { - return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + return Promise.resolve({ + exitCode: 0, + stdout: `${stdout}`, + stderr: '' + }); }); maxSatisfyingSpy.mockImplementation(() => inputVersion); @@ -91,9 +101,13 @@ describe('installer tests', () => { it(`should warn if the 'quality' input is set and the supplied version is in A.B.C syntax`, async () => { const inputVersion = '6.0.300'; const inputQuality = 'ga' as QualityOptions; - + const stdout = `Fictitious dotnet version ${inputVersion} is installed`; getExecOutputSpy.mockImplementation(() => { - return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + return Promise.resolve({ + exitCode: 0, + stdout: `${stdout}`, + stderr: '' + }); }); maxSatisfyingSpy.mockImplementation(() => inputVersion); @@ -112,9 +126,14 @@ describe('installer tests', () => { it(`should warn if the 'quality' input is set and version isn't in A.B.C syntax but major tag is lower then 6`, async () => { const inputVersion = '3.1'; const inputQuality = 'ga' as QualityOptions; + const stdout = `Fictitious dotnet version 3.1.100 is installed`; getExecOutputSpy.mockImplementation(() => { - return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + return Promise.resolve({ + exitCode: 0, + stdout: `${stdout}`, + stderr: '' + }); }); maxSatisfyingSpy.mockImplementation(() => inputVersion); @@ -135,10 +154,11 @@ describe('installer tests', () => { async inputVersion => { const inputQuality = 'ga' as QualityOptions; const exitCode = 0; + const stdout = `Fictitious dotnet version 6.0.0 is installed`; getExecOutputSpy.mockImplementation(() => { return Promise.resolve({ exitCode: exitCode, - stdout: '', + stdout: `${stdout}`, stderr: '' }); }); @@ -167,10 +187,11 @@ describe('installer tests', () => { async inputVersion => { const inputQuality = '' as QualityOptions; const exitCode = 0; + const stdout = `Fictitious dotnet version 6.0.0 is installed`; getExecOutputSpy.mockImplementation(() => { return Promise.resolve({ exitCode: exitCode, - stdout: '', + stdout: `${stdout}`, stderr: '' }); }); @@ -199,9 +220,14 @@ describe('installer tests', () => { process.env['https_proxy'] = 'https://proxy.com'; const inputVersion = '6.0.100'; const inputQuality = '' as QualityOptions; + const stdout = `Fictitious dotnet version ${inputVersion} is installed`; getExecOutputSpy.mockImplementation(() => { - return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + return Promise.resolve({ + exitCode: 0, + stdout: `${stdout}`, + stderr: '' + }); }); maxSatisfyingSpy.mockImplementation(() => inputVersion); @@ -225,9 +251,14 @@ describe('installer tests', () => { process.env['no_proxy'] = 'first.url,second.url'; const inputVersion = '6.0.100'; const inputQuality = '' as QualityOptions; + const stdout = `Fictitious dotnet version 6.0.0 is installed`; getExecOutputSpy.mockImplementation(() => { - return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + return Promise.resolve({ + exitCode: 0, + stdout: `${stdout}`, + stderr: '' + }); }); maxSatisfyingSpy.mockImplementation(() => inputVersion); @@ -396,4 +427,4 @@ describe('installer tests', () => { ); }); }); -}); \ No newline at end of file +}); From fefaa59d2eb646c641920b07bfc9897559f8d6d6 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Tue, 16 May 2023 14:58:18 +0200 Subject: [PATCH 24/32] update logic of outputting dotnet-version --- dist/index.js | 40 ++++++++++++++++++++++++------- src/installer.ts | 9 ++++--- src/setup-dotnet.ts | 58 +++++++++++++++++++++++++++++++++++---------- 3 files changed, 82 insertions(+), 25 deletions(-) diff --git a/dist/index.js b/dist/index.js index 51fc3a2..dc3e1f6 100644 --- a/dist/index.js +++ b/dist/index.js @@ -439,7 +439,8 @@ class DotnetCoreInstaller { const regex = /(?\d+\.\d+\.\d+[a-z0-9._-]*)/gm; const matchedResult = regex.exec(stdout); if (!matchedResult) { - throw new Error(`Failed to parse installed by the script version of .NET`); + core.warning(`Failed to parse installed by the script version of .NET`); + return null; } return matchedResult.groups.version; } @@ -577,13 +578,18 @@ function run() { if (sourceUrl) { auth.configAuthentication(sourceUrl, configFile); } - const comparisonRange = globalJsonFileInput - ? versions[versions.length - 1] - : '*'; - const versionToOutput = semver_1.default.maxSatisfying(installedDotnetVersions, comparisonRange, { - includePrerelease: true - }); - core.setOutput('dotnet-version', versionToOutput); + // const comparisonRange: string = globalJsonFileInput + // ? versions[versions.length - 1]! + // : '*'; + // const versionToOutput = semver.maxSatisfying( + // installedDotnetVersions, + // comparisonRange, + // { + // includePrerelease: true + // } + // ); + // core.setOutput('dotnet-version', versionToOutput); + outputInstalledVersion(installedDotnetVersions, globalJsonFileInput); const matchersPath = path_1.default.join(__dirname, '..', '.github'); core.info(`##[add-matcher]${path_1.default.join(matchersPath, 'csc.json')}`); } @@ -608,6 +614,24 @@ function getVersionFromGlobalJson(globalJsonPath) { } return version; } +function outputInstalledVersion(installedVersions, globalJsonFileInput) { + if (!installedVersions.length) { + core.info(`No .NET version was installed. The 'dotnet-version' output will not be set.`); + } + if (installedVersions.includes(null)) { + core.warning(`Failed to output the installed version of .NET. The 'dotnet-version' output will not be set.`); + return; + } + if (globalJsonFileInput) { + const versionToOutput = installedVersions.at(-1); + core.setOutput('dotnet-version', versionToOutput); + return; + } + const versionToOutput = semver_1.default.maxSatisfying(installedVersions, '*', { + includePrerelease: true + }); + core.setOutput('dotnet-version', versionToOutput); +} run(); diff --git a/src/installer.ts b/src/installer.ts index a12529f..d31ec00 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -197,7 +197,7 @@ export class DotnetCoreInstaller { } } - public async installDotnet(): Promise { + public async installDotnet(): Promise { const windowsDefaultOptions = [ '-NoLogo', '-Sta', @@ -271,14 +271,13 @@ export class DotnetCoreInstaller { return this.parseInstalledVersion(stdout); } - private parseInstalledVersion(stdout: string): string { + private parseInstalledVersion(stdout: string): string | null { const regex = /(?\d+\.\d+\.\d+[a-z0-9._-]*)/gm; const matchedResult = regex.exec(stdout); if (!matchedResult) { - throw new Error( - `Failed to parse installed by the script version of .NET` - ); + core.warning(`Failed to parse installed by the script version of .NET`); + return null; } return matchedResult.groups!.version; } diff --git a/src/setup-dotnet.ts b/src/setup-dotnet.ts index 31c80a2..1a4755a 100644 --- a/src/setup-dotnet.ts +++ b/src/setup-dotnet.ts @@ -27,7 +27,7 @@ export async function run() { // Proxy, auth, (etc) are still set up, even if no version is identified // const versions = core.getMultilineInput('dotnet-version'); - const installedDotnetVersions: string[] = []; + const installedDotnetVersions: (string | null)[] = []; const globalJsonFileInput = core.getInput('global-json-file'); if (globalJsonFileInput) { @@ -78,19 +78,20 @@ export async function run() { auth.configAuthentication(sourceUrl, configFile); } - const comparisonRange: string = globalJsonFileInput - ? versions[versions.length - 1]! - : '*'; + // const comparisonRange: string = globalJsonFileInput + // ? versions[versions.length - 1]! + // : '*'; - const versionToOutput = semver.maxSatisfying( - installedDotnetVersions, - comparisonRange, - { - includePrerelease: true - } - ); + // const versionToOutput = semver.maxSatisfying( + // installedDotnetVersions, + // comparisonRange, + // { + // includePrerelease: true + // } + // ); - core.setOutput('dotnet-version', versionToOutput); + // core.setOutput('dotnet-version', versionToOutput); + outputInstalledVersion(installedDotnetVersions, globalJsonFileInput); const matchersPath = path.join(__dirname, '..', '.github'); core.info(`##[add-matcher]${path.join(matchersPath, 'csc.json')}`); @@ -116,4 +117,37 @@ function getVersionFromGlobalJson(globalJsonPath: string): string { return version; } +function outputInstalledVersion( + installedVersions: (string | null)[], + globalJsonFileInput: string +): void { + if (!installedVersions.length) { + core.info( + `No .NET version was installed. The 'dotnet-version' output will not be set.` + ); + } + if (installedVersions.includes(null)) { + core.warning( + `Failed to output the installed version of .NET. The 'dotnet-version' output will not be set.` + ); + return; + } + + if (globalJsonFileInput) { + const versionToOutput = installedVersions.at(-1); + core.setOutput('dotnet-version', versionToOutput); + return; + } + + const versionToOutput = semver.maxSatisfying( + installedVersions as string[], + '*', + { + includePrerelease: true + } + ); + + core.setOutput('dotnet-version', versionToOutput); +} + run(); From 21cf89aa73470741965b60731b60d061054d3e05 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Tue, 16 May 2023 15:13:18 +0200 Subject: [PATCH 25/32] Remove commented lines of code --- dist/index.js | 11 ----------- src/setup-dotnet.ts | 13 ------------- 2 files changed, 24 deletions(-) diff --git a/dist/index.js b/dist/index.js index dc3e1f6..2b8903f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -578,17 +578,6 @@ function run() { if (sourceUrl) { auth.configAuthentication(sourceUrl, configFile); } - // const comparisonRange: string = globalJsonFileInput - // ? versions[versions.length - 1]! - // : '*'; - // const versionToOutput = semver.maxSatisfying( - // installedDotnetVersions, - // comparisonRange, - // { - // includePrerelease: true - // } - // ); - // core.setOutput('dotnet-version', versionToOutput); outputInstalledVersion(installedDotnetVersions, globalJsonFileInput); const matchersPath = path_1.default.join(__dirname, '..', '.github'); core.info(`##[add-matcher]${path_1.default.join(matchersPath, 'csc.json')}`); diff --git a/src/setup-dotnet.ts b/src/setup-dotnet.ts index 1a4755a..188257b 100644 --- a/src/setup-dotnet.ts +++ b/src/setup-dotnet.ts @@ -78,19 +78,6 @@ export async function run() { auth.configAuthentication(sourceUrl, configFile); } - // const comparisonRange: string = globalJsonFileInput - // ? versions[versions.length - 1]! - // : '*'; - - // const versionToOutput = semver.maxSatisfying( - // installedDotnetVersions, - // comparisonRange, - // { - // includePrerelease: true - // } - // ); - - // core.setOutput('dotnet-version', versionToOutput); outputInstalledVersion(installedDotnetVersions, globalJsonFileInput); const matchersPath = path.join(__dirname, '..', '.github'); From 2f028bc0440d3fe98ed5bb0dac9e76ee3d85bb1e Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Thu, 18 May 2023 11:11:51 +0200 Subject: [PATCH 26/32] update unit and e2e tests --- .github/workflows/e2e-tests.yml | 67 ++++++++++++++++++++++----------- __tests__/installer.test.ts | 36 ++++++++++++++++-- __tests__/setup-dotnet.test.ts | 31 ++++++++++++++- dist/index.js | 3 +- src/installer.ts | 2 +- src/setup-dotnet.ts | 2 + 6 files changed, 113 insertions(+), 28 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 6d00d5d..3933a34 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -133,6 +133,27 @@ jobs: shell: pwsh run: __tests__/verify-dotnet.ps1 -Patterns "^2.2", "^3.1" + test-ABCxx-syntax: + runs-on: ${{ matrix.operating-system }} + strategy: + fail-fast: false + matrix: + operating-system: [ubuntu-latest, windows-latest, macOS-latest] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Clear toolcache + shell: pwsh + run: __tests__/clear-toolcache.ps1 ${{ runner.os }} + + - name: Setup dotnet 6.0.4xx + uses: ./ + with: + dotnet-version: '6.0.4xx' + - name: Verify dotnet + shell: pwsh + run: __tests__/verify-dotnet.ps1 -Patterns "^6\.0\.4\d{2}" + test-setup-with-wildcard: runs-on: ${{ matrix.operating-system }} strategy: @@ -183,6 +204,31 @@ jobs: shell: pwsh run: __tests__/verify-dotnet.ps1 -Patterns "^2.2", "^3.1" + test-setup-global-json-only: + runs-on: ${{ matrix.operating-system }} + strategy: + fail-fast: false + matrix: + operating-system: [ubuntu-latest, windows-latest, macOS-latest] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Clear toolcache + shell: pwsh + run: __tests__/clear-toolcache.ps1 ${{ runner.os }} + - name: Write global.json + shell: bash + run: | + mkdir subdirectory + echo '{"sdk":{"version": "2.2.207","rollForward": "latestFeature"}}' > ./subdirectory/global.json + - name: Setup dotnet + uses: ./ + with: + global-json-file: ./subdirectory/global.json + - name: Verify dotnet + shell: pwsh + run: __tests__/verify-dotnet.ps1 -Patterns "^2.2" + test-setup-with-dotnet-quality: runs-on: ${{ matrix.operating-system }} strategy: @@ -205,27 +251,6 @@ jobs: shell: pwsh run: __tests__/verify-dotnet.ps1 -Patterns "^7\.0\.\d+-" - test-ABCxx-syntax: - runs-on: ${{ matrix.operating-system }} - strategy: - fail-fast: false - matrix: - operating-system: [ubuntu-latest, windows-latest, macOS-latest] - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Clear toolcache - shell: pwsh - run: __tests__/clear-toolcache.ps1 ${{ runner.os }} - - - name: Setup dotnet 6.0.4xx - uses: ./ - with: - dotnet-version: '6.0.4xx' - - name: Verify dotnet - shell: pwsh - run: __tests__/verify-dotnet.ps1 -Patterns "^6\.0\.4\d{2}" - test-dotnet-version-output-during-single-version-installation: runs-on: ${{ matrix.operating-system }} strategy: diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 0d264b5..08aa8e7 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -306,7 +306,8 @@ describe('installer tests', () => { '3.1.*', '3.1.X', '3.1.2', - '3.1.0-preview1' + '3.1.0-preview1', + '6.0.2xx' ]).test( 'if valid version is supplied (%s), it should return version object with some value', async version => { @@ -358,7 +359,16 @@ describe('installer tests', () => { } ); - each(['3', '3.1', '3.1.x', '3.1.*', '3.1.X']).test( + each([ + '3', + '3.1', + '3.1.x', + '3.1.*', + '3.1.X', + '6.0.2xx', + '6.0.2XX', + '6.0.2**' + ]).test( "if version that can be resolved to 'channel' option is supplied (%s), it should set type to 'channel' in version object", async version => { const dotnetVersionResolver = new installer.DotnetVersionResolver( @@ -373,7 +383,15 @@ describe('installer tests', () => { } ); - each(['6.0', '6.0.x', '6.0.*', '6.0.X']).test( + each([ + '6.0', + '6.0.x', + '6.0.*', + '6.0.X', + '6.0.2xx', + '6.0.2XX', + '6.0.2**' + ]).test( "if version that can be resolved to 'channel' option is supplied and its major tag is >= 6 (%s), it should set type to 'channel' and qualityFlag to 'true' in version object", async version => { const dotnetVersionResolver = new installer.DotnetVersionResolver( @@ -425,6 +443,18 @@ describe('installer tests', () => { } } ); + + it(`should throw if dotnet-version is supplied in A.B.Cxx syntax with major tag lower that 5`, async () => { + const version = '3.0.1xx'; + const dotnetVersionResolver = new installer.DotnetVersionResolver( + version + ); + await expect( + async () => await dotnetVersionResolver.createDotNetVersion() + ).rejects.toThrow( + `'dotnet-version' was supplied in invalid format: ${version}! The A.B.Cxx syntax is available since the .NET 5.0 release.` + ); + }); }); }); }); diff --git a/__tests__/setup-dotnet.test.ts b/__tests__/setup-dotnet.test.ts index 831408c..45043a1 100644 --- a/__tests__/setup-dotnet.test.ts +++ b/__tests__/setup-dotnet.test.ts @@ -12,6 +12,7 @@ describe('setup-dotnet tests', () => { const getInputSpy = jest.spyOn(core, 'getInput'); const getMultilineInputSpy = jest.spyOn(core, 'getMultilineInput'); const setFailedSpy = jest.spyOn(core, 'setFailed'); + const warningSpy = jest.spyOn(core, 'warning'); const debugSpy = jest.spyOn(core, 'debug'); const infoSpy = jest.spyOn(core, 'info'); const setOutputSpy = jest.spyOn(core, 'setOutput'); @@ -133,14 +134,40 @@ describe('setup-dotnet tests', () => { ); }); - it('should call setOutput() after installation complete', async () => { + it('should call setOutput() after installation complete successfully', async () => { inputs['dotnet-version'] = ['6.0.300']; - installDotnetSpy.mockImplementation(() => Promise.resolve('')); + installDotnetSpy.mockImplementation(() => + Promise.resolve(`${inputs['dotnet-version']}`) + ); addToPathSpy.mockImplementation(() => {}); await setup.run(); expect(setOutputSpy).toHaveBeenCalledTimes(1); }); + + it(`shouldn't call setOutput() if parsing dotnet-installer logs failed`, async () => { + inputs['dotnet-version'] = ['6.0.300']; + const warningMessage = `Failed to output the installed version of .NET. The 'dotnet-version' output will not be set.`; + + installDotnetSpy.mockImplementation(() => Promise.resolve(null)); + addToPathSpy.mockImplementation(() => {}); + + await setup.run(); + expect(warningSpy).toHaveBeenCalledWith(warningMessage); + expect(setOutputSpy).not.toHaveBeenCalled(); + }); + + it(`shouldn't call setOutput() if actions didn't install .NET`, async () => { + inputs['dotnet-version'] = []; + const warningMessage = `No .NET version was installed. The 'dotnet-version' output will not be set.`; + + addToPathSpy.mockImplementation(() => {}); + + await setup.run(); + + expect(infoSpy).toHaveBeenCalledWith(warningMessage); + expect(setOutputSpy).not.toHaveBeenCalled(); + }); }); }); diff --git a/dist/index.js b/dist/index.js index 2b8903f..aca3dd0 100644 --- a/dist/index.js +++ b/dist/index.js @@ -270,7 +270,7 @@ class DotnetVersionResolver { } isLatestPatchSyntax() { var _b, _c; - const majorTag = (_c = (_b = this.inputVersion.match(/^(?\d+)\.\d+\.\d{1}x{2}$/)) === null || _b === void 0 ? void 0 : _b.groups) === null || _c === void 0 ? void 0 : _c.majorTag; + const majorTag = (_c = (_b = this.inputVersion.match(/^(?\d+)\.\d+\.\d{1}(x|X|\*){2}$/)) === null || _b === void 0 ? void 0 : _b.groups) === null || _c === void 0 ? void 0 : _c.majorTag; if (majorTag && parseInt(majorTag) < DotnetInstallerLimits.LatestPatchSyntaxMinimalMajorTag) { @@ -606,6 +606,7 @@ function getVersionFromGlobalJson(globalJsonPath) { function outputInstalledVersion(installedVersions, globalJsonFileInput) { if (!installedVersions.length) { core.info(`No .NET version was installed. The 'dotnet-version' output will not be set.`); + return; } if (installedVersions.includes(null)) { core.warning(`Failed to output the installed version of .NET. The 'dotnet-version' output will not be set.`); diff --git a/src/installer.ts b/src/installer.ts index d31ec00..e4c3ffe 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -49,7 +49,7 @@ export class DotnetVersionResolver { private isLatestPatchSyntax() { const majorTag = this.inputVersion.match( - /^(?\d+)\.\d+\.\d{1}x{2}$/ + /^(?\d+)\.\d+\.\d{1}(x|X|\*){2}$/ )?.groups?.majorTag; if ( majorTag && diff --git a/src/setup-dotnet.ts b/src/setup-dotnet.ts index 188257b..e7e7726 100644 --- a/src/setup-dotnet.ts +++ b/src/setup-dotnet.ts @@ -112,7 +112,9 @@ function outputInstalledVersion( core.info( `No .NET version was installed. The 'dotnet-version' output will not be set.` ); + return; } + if (installedVersions.includes(null)) { core.warning( `Failed to output the installed version of .NET. The 'dotnet-version' output will not be set.` From 898aa0ce4d1e9419e27e53897ec70fd148115484 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Thu, 18 May 2023 11:16:22 +0200 Subject: [PATCH 27/32] Fix typo --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index eec3975..acac654 100644 --- a/action.yml +++ b/action.yml @@ -6,7 +6,7 @@ branding: color: green inputs: dotnet-version: - description: 'Optional SDK version(s) to use. If not provided, will install global.json version when available. Examples: 2.2.104, 3.1, 3.1.x, 3.x, 3.1.4xx' + description: 'Optional SDK version(s) to use. If not provided, will install global.json version when available. Examples: 2.2.104, 3.1, 3.1.x, 3.x, 6.0.2xx' dotnet-quality: description: 'Optional quality of the build. The possible values are: daily, signed, validated, preview, ga.' global-json-file: From 83a1653fa32f4cf123fe3aec2962ff47ffd65859 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Thu, 18 May 2023 11:46:40 +0200 Subject: [PATCH 28/32] Update regular expresiion for isLatestPatchSyntax() --- __tests__/installer.test.ts | 21 ++------------------- dist/index.js | 2 +- src/installer.ts | 2 +- 3 files changed, 4 insertions(+), 21 deletions(-) diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 08aa8e7..808d34a 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -359,16 +359,7 @@ describe('installer tests', () => { } ); - each([ - '3', - '3.1', - '3.1.x', - '3.1.*', - '3.1.X', - '6.0.2xx', - '6.0.2XX', - '6.0.2**' - ]).test( + each(['3', '3.1', '3.1.x', '3.1.*', '3.1.X', '6.0.2xx']).test( "if version that can be resolved to 'channel' option is supplied (%s), it should set type to 'channel' in version object", async version => { const dotnetVersionResolver = new installer.DotnetVersionResolver( @@ -383,15 +374,7 @@ describe('installer tests', () => { } ); - each([ - '6.0', - '6.0.x', - '6.0.*', - '6.0.X', - '6.0.2xx', - '6.0.2XX', - '6.0.2**' - ]).test( + each(['6.0', '6.0.x', '6.0.*', '6.0.X', '6.0.2xx']).test( "if version that can be resolved to 'channel' option is supplied and its major tag is >= 6 (%s), it should set type to 'channel' and qualityFlag to 'true' in version object", async version => { const dotnetVersionResolver = new installer.DotnetVersionResolver( diff --git a/dist/index.js b/dist/index.js index aca3dd0..093671e 100644 --- a/dist/index.js +++ b/dist/index.js @@ -270,7 +270,7 @@ class DotnetVersionResolver { } isLatestPatchSyntax() { var _b, _c; - const majorTag = (_c = (_b = this.inputVersion.match(/^(?\d+)\.\d+\.\d{1}(x|X|\*){2}$/)) === null || _b === void 0 ? void 0 : _b.groups) === null || _c === void 0 ? void 0 : _c.majorTag; + const majorTag = (_c = (_b = this.inputVersion.match(/^(?\d+)\.\d+\.\d{1}x{2}$/)) === null || _b === void 0 ? void 0 : _b.groups) === null || _c === void 0 ? void 0 : _c.majorTag; if (majorTag && parseInt(majorTag) < DotnetInstallerLimits.LatestPatchSyntaxMinimalMajorTag) { diff --git a/src/installer.ts b/src/installer.ts index e4c3ffe..d31ec00 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -49,7 +49,7 @@ export class DotnetVersionResolver { private isLatestPatchSyntax() { const majorTag = this.inputVersion.match( - /^(?\d+)\.\d+\.\d{1}(x|X|\*){2}$/ + /^(?\d+)\.\d+\.\d{1}x{2}$/ )?.groups?.majorTag; if ( majorTag && From 3cf3e230c1d440ed4250ff9a4f8ef1f2b7bc19d6 Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Thu, 18 May 2023 12:01:55 +0200 Subject: [PATCH 29/32] Fix informational message --- dist/index.js | 2 +- src/installer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/index.js b/dist/index.js index 093671e..649f940 100644 --- a/dist/index.js +++ b/dist/index.js @@ -371,7 +371,7 @@ class DotnetCoreInstaller { scriptArguments.push(option, this.quality); } else { - core.warning(`'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A and A.x formats where the major tag is higher than 5. You specified: ${this.version}. 'dotnet-quality' input is ignored.`); + core.warning(`'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A, A.x and A.B.Cxx formats where the major tag is higher than 5. You specified: ${this.version}. 'dotnet-quality' input is ignored.`); } } installDotnet() { diff --git a/src/installer.ts b/src/installer.ts index d31ec00..18aef22 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -192,7 +192,7 @@ export class DotnetCoreInstaller { scriptArguments.push(option, this.quality); } else { core.warning( - `'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A and A.x formats where the major tag is higher than 5. You specified: ${this.version}. 'dotnet-quality' input is ignored.` + `'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A, A.x and A.B.Cxx formats where the major tag is higher than 5. You specified: ${this.version}. 'dotnet-quality' input is ignored.` ); } } From 38b49fb7178824be5cb7b9acd01eb2627e719a0b Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Thu, 18 May 2023 12:39:22 +0200 Subject: [PATCH 30/32] Fix informational and debug messages --- __tests__/installer.test.ts | 4 ++-- __tests__/setup-dotnet.test.ts | 6 +++--- dist/index.js | 12 ++++++------ src/installer.ts | 6 +++--- src/setup-dotnet.ts | 8 +++----- 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 808d34a..c9f815e 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -119,7 +119,7 @@ describe('installer tests', () => { await dotnetInstaller.installDotnet(); expect(warningSpy).toHaveBeenCalledWith( - `'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A and A.x formats where the major tag is higher than 5. You specified: ${inputVersion}. 'dotnet-quality' input is ignored.` + `The 'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A, A.x and A.B.Cxx formats where the major tag is higher than 5. You specified: ${inputVersion}. 'dotnet-quality' input is ignored.` ); }); @@ -145,7 +145,7 @@ describe('installer tests', () => { await dotnetInstaller.installDotnet(); expect(warningSpy).toHaveBeenCalledWith( - `'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A and A.x formats where the major tag is higher than 5. You specified: ${inputVersion}. 'dotnet-quality' input is ignored.` + `The 'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A, A.x and A.B.Cxx formats where the major tag is higher than 5. You specified: ${inputVersion}. 'dotnet-quality' input is ignored.` ); }); diff --git a/__tests__/setup-dotnet.test.ts b/__tests__/setup-dotnet.test.ts index 45043a1..9e4b8ca 100644 --- a/__tests__/setup-dotnet.test.ts +++ b/__tests__/setup-dotnet.test.ts @@ -59,7 +59,7 @@ describe('setup-dotnet tests', () => { const expectedDebugMessage = 'No version found, trying to find version from global.json'; - const expectedInfoMessage = `global.json wasn't found in the root directory. No .NET version will be installed.`; + const expectedInfoMessage = `A global.json wasn't found in the root directory. No .NET version will be installed.`; await setup.run(); @@ -73,7 +73,7 @@ describe('setup-dotnet tests', () => { inputs['dotnet-version'] = ['6.0']; inputs['dotnet-quality'] = 'fictitiousQuality'; - const expectedErrorMessage = `${inputs['dotnet-quality']} is not a supported value for 'dotnet-quality' option. Supported values are: daily, signed, validated, preview, ga.`; + const expectedErrorMessage = `Value '${inputs['dotnet-quality']}' is not supported for the 'dotnet-quality' option. Supported values are: daily, signed, validated, preview, ga.`; await setup.run(); expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage); @@ -160,7 +160,7 @@ describe('setup-dotnet tests', () => { it(`shouldn't call setOutput() if actions didn't install .NET`, async () => { inputs['dotnet-version'] = []; - const warningMessage = `No .NET version was installed. The 'dotnet-version' output will not be set.`; + const warningMessage = `The 'dotnet-version' output will not be set.`; addToPathSpy.mockImplementation(() => {}); diff --git a/dist/index.js b/dist/index.js index 649f940..90e197b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -255,7 +255,7 @@ class DotnetVersionResolver { resolveVersionInput() { return __awaiter(this, void 0, void 0, function* () { if (!semver_1.default.validRange(this.inputVersion) && !this.isLatestPatchSyntax()) { - throw new Error(`'dotnet-version' was supplied in invalid format: ${this.inputVersion}! Supported syntax: A.B.C, A.B, A.B.x, A, A.x, A.B.Cxx`); + throw new Error(`The 'dotnet-version' was supplied in invalid format: ${this.inputVersion}! Supported syntax: A.B.C, A.B, A.B.x, A, A.x, A.B.Cxx`); } if (semver_1.default.valid(this.inputVersion)) { this.createVersionArgument(); @@ -274,7 +274,7 @@ class DotnetVersionResolver { if (majorTag && parseInt(majorTag) < DotnetInstallerLimits.LatestPatchSyntaxMinimalMajorTag) { - throw new Error(`'dotnet-version' was supplied in invalid format: ${this.inputVersion}! The A.B.Cxx syntax is available since the .NET 5.0 release.`); + throw new Error(`The 'dotnet-version' was supplied in invalid format: ${this.inputVersion}! The A.B.Cxx syntax is available since the .NET 5.0 release.`); } return majorTag ? true : false; } @@ -371,7 +371,7 @@ class DotnetCoreInstaller { scriptArguments.push(option, this.quality); } else { - core.warning(`'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A, A.x and A.B.Cxx formats where the major tag is higher than 5. You specified: ${this.version}. 'dotnet-quality' input is ignored.`); + core.warning(`The 'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A, A.x and A.B.Cxx formats where the major tag is higher than 5. You specified: ${this.version}. 'dotnet-quality' input is ignored.`); } } installDotnet() { @@ -556,13 +556,13 @@ function run() { versions.push(getVersionFromGlobalJson(globalJsonPath)); } else { - core.info(`global.json wasn't found in the root directory. No .NET version will be installed.`); + core.info(`A global.json wasn't found in the root directory. No .NET version will be installed.`); } } if (versions.length) { const quality = core.getInput('dotnet-quality'); if (quality && !qualityOptions.includes(quality)) { - throw new Error(`${quality} is not a supported value for 'dotnet-quality' option. Supported values are: daily, signed, validated, preview, ga.`); + throw new Error(`Value '${quality}' is not supported for the 'dotnet-quality' option. Supported values are: daily, signed, validated, preview, ga.`); } let dotnetInstaller; const uniqueVersions = new Set(versions); @@ -605,7 +605,7 @@ function getVersionFromGlobalJson(globalJsonPath) { } function outputInstalledVersion(installedVersions, globalJsonFileInput) { if (!installedVersions.length) { - core.info(`No .NET version was installed. The 'dotnet-version' output will not be set.`); + core.info(`The 'dotnet-version' output will not be set.`); return; } if (installedVersions.includes(null)) { diff --git a/src/installer.ts b/src/installer.ts index 18aef22..f86dfa0 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -33,7 +33,7 @@ export class DotnetVersionResolver { private async resolveVersionInput(): Promise { if (!semver.validRange(this.inputVersion) && !this.isLatestPatchSyntax()) { throw new Error( - `'dotnet-version' was supplied in invalid format: ${this.inputVersion}! Supported syntax: A.B.C, A.B, A.B.x, A, A.x, A.B.Cxx` + `The 'dotnet-version' was supplied in invalid format: ${this.inputVersion}! Supported syntax: A.B.C, A.B, A.B.x, A, A.x, A.B.Cxx` ); } if (semver.valid(this.inputVersion)) { @@ -57,7 +57,7 @@ export class DotnetVersionResolver { DotnetInstallerLimits.LatestPatchSyntaxMinimalMajorTag ) { throw new Error( - `'dotnet-version' was supplied in invalid format: ${this.inputVersion}! The A.B.Cxx syntax is available since the .NET 5.0 release.` + `The 'dotnet-version' was supplied in invalid format: ${this.inputVersion}! The A.B.Cxx syntax is available since the .NET 5.0 release.` ); } return majorTag ? true : false; @@ -192,7 +192,7 @@ export class DotnetCoreInstaller { scriptArguments.push(option, this.quality); } else { core.warning( - `'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A, A.x and A.B.Cxx formats where the major tag is higher than 5. You specified: ${this.version}. 'dotnet-quality' input is ignored.` + `The 'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A, A.x and A.B.Cxx formats where the major tag is higher than 5. You specified: ${this.version}. 'dotnet-quality' input is ignored.` ); } } diff --git a/src/setup-dotnet.ts b/src/setup-dotnet.ts index e7e7726..cf26f14 100644 --- a/src/setup-dotnet.ts +++ b/src/setup-dotnet.ts @@ -48,7 +48,7 @@ export async function run() { versions.push(getVersionFromGlobalJson(globalJsonPath)); } else { core.info( - `global.json wasn't found in the root directory. No .NET version will be installed.` + `A global.json wasn't found in the root directory. No .NET version will be installed.` ); } } @@ -58,7 +58,7 @@ export async function run() { if (quality && !qualityOptions.includes(quality)) { throw new Error( - `${quality} is not a supported value for 'dotnet-quality' option. Supported values are: daily, signed, validated, preview, ga.` + `Value '${quality}' is not supported for the 'dotnet-quality' option. Supported values are: daily, signed, validated, preview, ga.` ); } @@ -109,9 +109,7 @@ function outputInstalledVersion( globalJsonFileInput: string ): void { if (!installedVersions.length) { - core.info( - `No .NET version was installed. The 'dotnet-version' output will not be set.` - ); + core.info(`The 'dotnet-version' output will not be set.`); return; } From 5fdecd2063d6b33f52291b3d9299f6a7d4a84964 Mon Sep 17 00:00:00 2001 From: Nikolai Laevskii <130154213+nikolai-laevskii@users.noreply.github.com> Date: Fri, 19 May 2023 08:50:54 +0200 Subject: [PATCH 31/32] Increase amount of retries for Dotnet installation scripts tests (#427) * Increase amount of retries for Dotnet installation scripts tests * Format: Increase amount of retries for Dotnet installation scripts tests --- __tests__/installation-scripts.test.ts | 91 +++++++++++++++----------- 1 file changed, 52 insertions(+), 39 deletions(-) diff --git a/__tests__/installation-scripts.test.ts b/__tests__/installation-scripts.test.ts index e309a98..ce71ea7 100644 --- a/__tests__/installation-scripts.test.ts +++ b/__tests__/installation-scripts.test.ts @@ -2,46 +2,59 @@ import path from 'path'; import fs from 'fs'; import * as hc from '@actions/http-client'; -describe('Dotnet installation scripts tests', () => { - it('Uses an up to date bash download script', async () => { - const httpCallbackClient = new hc.HttpClient('setup-dotnet-test', [], { - allowRetries: true, - maxRetries: 3 - }); - const response: hc.HttpClientResponse = await httpCallbackClient.get( - 'https://dot.net/v1/dotnet-install.sh' - ); - expect(response.message.statusCode).toBe(200); - const upToDateContents: string = await response.readBody(); - const currentContents: string = fs - .readFileSync( - path.join(__dirname, '..', 'externals', 'install-dotnet.sh') - ) - .toString(); - expect(normalizeFileContents(currentContents)).toBe( - normalizeFileContents(upToDateContents) - ); - }, 30000); +const HTTP_CLIENT_OPTIONS = {allowRetries: true, maxRetries: 10} as const; +const TEST_TIMEOUT = 30000; - it('Uses an up to date powershell download script', async () => { - const httpCallbackClient = new hc.HttpClient('setup-dotnet-test', [], { - allowRetries: true, - maxRetries: 3 - }); - const response: hc.HttpClientResponse = await httpCallbackClient.get( - 'https://dot.net/v1/dotnet-install.ps1' - ); - expect(response.message.statusCode).toBe(200); - const upToDateContents: string = await response.readBody(); - const currentContents: string = fs - .readFileSync( - path.join(__dirname, '..', 'externals', 'install-dotnet.ps1') - ) - .toString(); - expect(normalizeFileContents(currentContents)).toBe( - normalizeFileContents(upToDateContents) - ); - }, 30000); +describe('Dotnet installation scripts tests', () => { + it( + 'Uses an up to date bash download script', + async () => { + const httpCallbackClient = new hc.HttpClient( + 'setup-dotnet-test', + [], + HTTP_CLIENT_OPTIONS + ); + const response: hc.HttpClientResponse = await httpCallbackClient.get( + 'https://dot.net/v1/dotnet-install.sh' + ); + expect(response.message.statusCode).toBe(200); + const upToDateContents: string = await response.readBody(); + const currentContents: string = fs + .readFileSync( + path.join(__dirname, '..', 'externals', 'install-dotnet.sh') + ) + .toString(); + expect(normalizeFileContents(currentContents)).toBe( + normalizeFileContents(upToDateContents) + ); + }, + TEST_TIMEOUT + ); + + it( + 'Uses an up to date powershell download script', + async () => { + const httpCallbackClient = new hc.HttpClient( + 'setup-dotnet-test', + [], + HTTP_CLIENT_OPTIONS + ); + const response: hc.HttpClientResponse = await httpCallbackClient.get( + 'https://dot.net/v1/dotnet-install.ps1' + ); + expect(response.message.statusCode).toBe(200); + const upToDateContents: string = await response.readBody(); + const currentContents: string = fs + .readFileSync( + path.join(__dirname, '..', 'externals', 'install-dotnet.ps1') + ) + .toString(); + expect(normalizeFileContents(currentContents)).toBe( + normalizeFileContents(upToDateContents) + ); + }, + TEST_TIMEOUT + ); }); function normalizeFileContents(contents: string): string { From b05a3f26b3adff9936a0ef8b806a7d9a57b72a6d Mon Sep 17 00:00:00 2001 From: IvanZosimov Date: Mon, 22 May 2023 12:27:33 +0200 Subject: [PATCH 32/32] Fix review points, rebuild solution --- __tests__/setup-dotnet.test.ts | 2 +- dist/index.js | 18 ++++++------------ src/installer.ts | 14 ++++---------- src/setup-dotnet.ts | 4 ++-- 4 files changed, 13 insertions(+), 25 deletions(-) diff --git a/__tests__/setup-dotnet.test.ts b/__tests__/setup-dotnet.test.ts index 9e4b8ca..2a32d63 100644 --- a/__tests__/setup-dotnet.test.ts +++ b/__tests__/setup-dotnet.test.ts @@ -59,7 +59,7 @@ describe('setup-dotnet tests', () => { const expectedDebugMessage = 'No version found, trying to find version from global.json'; - const expectedInfoMessage = `A global.json wasn't found in the root directory. No .NET version will be installed.`; + const expectedInfoMessage = `The global.json wasn't found in the root directory. No .NET version will be installed.`; await setup.run(); diff --git a/dist/index.js b/dist/index.js index 90e197b..088b68f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -242,11 +242,8 @@ const path_1 = __importDefault(__nccwpck_require__(1017)); const os_1 = __importDefault(__nccwpck_require__(2037)); const semver_1 = __importDefault(__nccwpck_require__(5911)); const utils_1 = __nccwpck_require__(918); -var DotnetInstallerLimits; -(function (DotnetInstallerLimits) { - DotnetInstallerLimits[DotnetInstallerLimits["QualityInputMinimalMajorTag"] = 6] = "QualityInputMinimalMajorTag"; - DotnetInstallerLimits[DotnetInstallerLimits["LatestPatchSyntaxMinimalMajorTag"] = 5] = "LatestPatchSyntaxMinimalMajorTag"; -})(DotnetInstallerLimits || (DotnetInstallerLimits = {})); +const QUALITY_INPUT_MINIMAL_MAJOR_TAG = 6; +const LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG = 5; class DotnetVersionResolver { constructor(version) { this.inputVersion = version.trim(); @@ -272,8 +269,7 @@ class DotnetVersionResolver { var _b, _c; const majorTag = (_c = (_b = this.inputVersion.match(/^(?\d+)\.\d+\.\d{1}x{2}$/)) === null || _b === void 0 ? void 0 : _b.groups) === null || _c === void 0 ? void 0 : _c.majorTag; if (majorTag && - parseInt(majorTag) < - DotnetInstallerLimits.LatestPatchSyntaxMinimalMajorTag) { + parseInt(majorTag) < LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG) { throw new Error(`The 'dotnet-version' was supplied in invalid format: ${this.inputVersion}! The A.B.Cxx syntax is available since the .NET 5.0 release.`); } return majorTag ? true : false; @@ -300,9 +296,7 @@ class DotnetVersionResolver { this.resolvedArgument.value = 'LTS'; } this.resolvedArgument.qualityFlag = - parseInt(major) >= DotnetInstallerLimits.QualityInputMinimalMajorTag - ? true - : false; + parseInt(major) >= QUALITY_INPUT_MINIMAL_MAJOR_TAG ? true : false; }); } createDotNetVersion() { @@ -556,7 +550,7 @@ function run() { versions.push(getVersionFromGlobalJson(globalJsonPath)); } else { - core.info(`A global.json wasn't found in the root directory. No .NET version will be installed.`); + core.info(`The global.json wasn't found in the root directory. No .NET version will be installed.`); } } if (versions.length) { @@ -613,7 +607,7 @@ function outputInstalledVersion(installedVersions, globalJsonFileInput) { return; } if (globalJsonFileInput) { - const versionToOutput = installedVersions.at(-1); + const versionToOutput = installedVersions.at(-1); // .NET SDK version parsed from the global.json file is installed last core.setOutput('dotnet-version', versionToOutput); return; } diff --git a/src/installer.ts b/src/installer.ts index f86dfa0..a993be8 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -16,11 +16,8 @@ export interface DotnetVersion { qualityFlag: boolean; } -enum DotnetInstallerLimits { - QualityInputMinimalMajorTag = 6, - LatestPatchSyntaxMinimalMajorTag = 5 -} - +const QUALITY_INPUT_MINIMAL_MAJOR_TAG = 6; +const LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG = 5; export class DotnetVersionResolver { private inputVersion: string; private resolvedArgument: DotnetVersion; @@ -53,8 +50,7 @@ export class DotnetVersionResolver { )?.groups?.majorTag; if ( majorTag && - parseInt(majorTag) < - DotnetInstallerLimits.LatestPatchSyntaxMinimalMajorTag + parseInt(majorTag) < LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG ) { throw new Error( `The 'dotnet-version' was supplied in invalid format: ${this.inputVersion}! The A.B.Cxx syntax is available since the .NET 5.0 release.` @@ -82,9 +78,7 @@ export class DotnetVersionResolver { this.resolvedArgument.value = 'LTS'; } this.resolvedArgument.qualityFlag = - parseInt(major) >= DotnetInstallerLimits.QualityInputMinimalMajorTag - ? true - : false; + parseInt(major) >= QUALITY_INPUT_MINIMAL_MAJOR_TAG ? true : false; } public async createDotNetVersion(): Promise { diff --git a/src/setup-dotnet.ts b/src/setup-dotnet.ts index cf26f14..58de8ef 100644 --- a/src/setup-dotnet.ts +++ b/src/setup-dotnet.ts @@ -48,7 +48,7 @@ export async function run() { versions.push(getVersionFromGlobalJson(globalJsonPath)); } else { core.info( - `A global.json wasn't found in the root directory. No .NET version will be installed.` + `The global.json wasn't found in the root directory. No .NET version will be installed.` ); } } @@ -121,7 +121,7 @@ function outputInstalledVersion( } if (globalJsonFileInput) { - const versionToOutput = installedVersions.at(-1); + const versionToOutput = installedVersions.at(-1); // .NET SDK version parsed from the global.json file is installed last core.setOutput('dotnet-version', versionToOutput); return; }