From bedc1923b0c69cd0c6dcbb480e8915e2f49bd980 Mon Sep 17 00:00:00 2001 From: Noah Gilson Date: Mon, 24 Feb 2025 16:00:02 -0800 Subject: [PATCH] Add additional version spec options to match rollforward from global json (#2139) * Add additional version spec options to match rollforward from global.json Resolves https://github.com/dotnet/vscode-dotnet-runtime/issues/2138 Need to add some tests, this logic is very WIP As requested by @lifengl * Add a lot of tests * add EVEN more tests * fix typo thank you @joeloff * Fix tests where ' caused build error * Fix random number generation Thank you @Forgind for catching this here: https://github.com/dotnet/vscode-dotnet-runtime/pull/2135/files#r1965980900 where there's more information, I'm going to add this fix into this PR so it's easier for everyone * fix logic for when perc is 100 perc of the time --- .../Acquisition/DotnetConditionValidator.ts | 79 +++++++++-- .../src/DotnetVersionSpecRequirement.ts | 15 +- .../src/EventStream/EventStreamEvents.ts | 2 +- .../unit/DotnetConditionValidator.test.ts | 134 ++++++++++++++++-- 4 files changed, 204 insertions(+), 26 deletions(-) diff --git a/vscode-dotnet-runtime-library/src/Acquisition/DotnetConditionValidator.ts b/vscode-dotnet-runtime-library/src/Acquisition/DotnetConditionValidator.ts index e132fcf8c3..57eee2e34c 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/DotnetConditionValidator.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/DotnetConditionValidator.ts @@ -2,19 +2,21 @@ * Licensed to the .NET Foundation under one or more agreements. * The .NET Foundation licenses this file to you under the MIT license. *--------------------------------------------------------------------------------------------*/ +import * as os from 'os'; +import { DotnetFindPathDidNotMeetCondition, DotnetUnableToCheckPATHArchitecture } from '../EventStream/EventStreamEvents'; import { IDotnetFindPathContext } from '../IDotnetFindPathContext'; import { CommandExecutor } from '../Utils/CommandExecutor'; +import { FileUtilities } from '../Utils/FileUtilities'; import { ICommandExecutor } from '../Utils/ICommandExecutor'; import { IUtilityContext } from '../Utils/IUtilityContext'; -import { IDotnetListInfo } from './IDotnetListInfo'; +import { DOTNET_INFORMATION_CACHE_DURATION_MS, SYS_CMD_SEARCH_CACHE_DURATION_MS } from './CacheTimeConstants'; import { IAcquisitionWorkerContext } from './IAcquisitionWorkerContext'; import { IDotnetConditionValidator } from './IDotnetConditionValidator'; +import { IDotnetListInfo } from './IDotnetListInfo'; import * as versionUtils from './VersionUtilities'; -import * as os from 'os'; -import { FileUtilities } from '../Utils/FileUtilities'; -import { DotnetFindPathDidNotMeetCondition, DotnetUnableToCheckPATHArchitecture } from '../EventStream/EventStreamEvents'; -import { DOTNET_INFORMATION_CACHE_DURATION_MS, SYS_CMD_SEARCH_CACHE_DURATION_MS } from './CacheTimeConstants'; +type simplifiedVersionSpec = 'equal' | 'greater_than_or_equal' | 'less_than_or_equal' | + 'latestPatch' | 'latestFeature'; export class DotnetConditionValidator implements IDotnetConditionValidator { @@ -108,7 +110,7 @@ Please set the PATH to a dotnet host that matches the architecture ${requirement const findSDKsCommand = await this.setCodePage() ? CommandExecutor.makeCommand(`chcp`, [`65001`, `|`, `"${existingPath}"`, '--list-sdks']) : CommandExecutor.makeCommand(`"${existingPath}"`, ['--list-sdks']); - const sdkInfo = await (this.executor!).execute(findSDKsCommand, {dotnetInstallToolCacheTtlMs: DOTNET_INFORMATION_CACHE_DURATION_MS}, false).then((result) => + const sdkInfo = await (this.executor!).execute(findSDKsCommand, { dotnetInstallToolCacheTtlMs: DOTNET_INFORMATION_CACHE_DURATION_MS }, false).then((result) => { const sdks = result.stdout.split('\n').map((line) => line.trim()).filter((line) => line.length > 0); const sdkInfos: IDotnetListInfo[] = sdks.map((sdk) => @@ -131,10 +133,10 @@ Please set the PATH to a dotnet host that matches the architecture ${requirement { // For Windows, we need to change the code page to UTF-8 to handle the output of the command. https://github.com/nodejs/node-v0.x-archive/issues/2190 // Only certain builds of windows support UTF 8 so we need to check that we can use it. - return os.platform() === 'win32' ? (await this.executor!.tryFindWorkingCommand([CommandExecutor.makeCommand('chcp', ['65001'])], {dotnetInstallToolCacheTtlMs: SYS_CMD_SEARCH_CACHE_DURATION_MS})) !== null : false; + return os.platform() === 'win32' ? (await this.executor!.tryFindWorkingCommand([CommandExecutor.makeCommand('chcp', ['65001'])], { dotnetInstallToolCacheTtlMs: SYS_CMD_SEARCH_CACHE_DURATION_MS })) !== null : false; } - private stringVersionMeetsRequirement(availableVersion: string, requestedVersion: string, requirement: IDotnetFindPathContext): boolean + public stringVersionMeetsRequirement(availableVersion: string, requestedVersion: string, requirement: IDotnetFindPathContext): boolean { const availableMajor = Number(versionUtils.getMajor(availableVersion, this.workerContext.eventStream, this.workerContext)); const requestedMajor = Number(versionUtils.getMajor(requestedVersion, this.workerContext.eventStream, this.workerContext)); @@ -142,6 +144,19 @@ Please set the PATH to a dotnet host that matches the architecture ${requirement : versionUtils.getSDKCompleteBandAndPatchVersionString(requestedVersion, this.workerContext.eventStream, this.workerContext); const requestedPatch = requestedPatchStr ? Number(requestedPatchStr) : null; + const adjustedVersionSpec: simplifiedVersionSpec = [requirement.versionSpecRequirement].map(x => + { + switch (x) + { + case 'latestMajor': + return 'greater_than_or_equal'; + case 'disable': + return 'equal'; + default: + return x; + } + }).at(0)!; + if (availableMajor === requestedMajor) { const availableMinor = Number(versionUtils.getMinor(availableVersion, this.workerContext.eventStream, this.workerContext)); @@ -149,23 +164,51 @@ Please set the PATH to a dotnet host that matches the architecture ${requirement if (availableMinor === requestedMinor && requestedPatch) { - const availablePatchStr: string | null = requirement.acquireContext.mode !== 'sdk' ? versionUtils.getRuntimePatchVersionString(availableVersion, this.workerContext.eventStream, this.workerContext) - : versionUtils.getSDKCompleteBandAndPatchVersionString(availableVersion, this.workerContext.eventStream, this.workerContext); + const availablePatchStr: string | null = requirement.acquireContext.mode !== 'sdk' ? + versionUtils.getRuntimePatchVersionString(availableVersion, this.workerContext.eventStream, this.workerContext) + : + (() => + { + const band = versionUtils.getSDKCompleteBandAndPatchVersionString(availableVersion, this.workerContext.eventStream, this.workerContext); + if (band) + { + return band; + } + return null; + })(); const availablePatch = availablePatchStr ? Number(availablePatchStr) : null; - switch (requirement.versionSpecRequirement) + + const availableBandStr: string | null = requirement.acquireContext.mode === 'sdk' ? + (() => + { + const featureBand = versionUtils.getFeatureBandFromVersion(availableVersion, this.workerContext.eventStream, this.workerContext, false); + if (featureBand) + { + return featureBand; + } + return null; + })() : null; + const availableBand = availableBandStr ? Number(availableBandStr) : null; + + switch (adjustedVersionSpec) { + // the 'availablePatch' must exist, since the version is from --list-runtimes or --list-sdks. case 'equal': return availablePatch === requestedPatch; case 'greater_than_or_equal': - // the 'availablePatch' must exist, since the version is from --list-runtimes or --list-sdks. + case 'latestFeature': return availablePatch! >= requestedPatch; case 'less_than_or_equal': return availablePatch! <= requestedPatch; + case 'latestPatch': + const requestedBandStr = requirement.acquireContext.mode === 'sdk' ? versionUtils.getFeatureBandFromVersion(requestedVersion, this.workerContext.eventStream, this.workerContext, false) ?? null : null; + const requestedBand = requestedBandStr ? Number(requestedBandStr) : null; + return availablePatch! >= requestedPatch && (availableBand ? availableBand === requestedBand : true); } } else { - switch (requirement.versionSpecRequirement) + switch (adjustedVersionSpec) { case 'equal': return availableMinor === requestedMinor; @@ -173,12 +216,15 @@ Please set the PATH to a dotnet host that matches the architecture ${requirement return availableMinor >= requestedMinor; case 'less_than_or_equal': return availableMinor <= requestedMinor; + case 'latestPatch': + case 'latestFeature': + return false } } } else { - switch (requirement.versionSpecRequirement) + switch (adjustedVersionSpec) { case 'equal': return false; @@ -186,6 +232,9 @@ Please set the PATH to a dotnet host that matches the architecture ${requirement return availableMajor >= requestedMajor; case 'less_than_or_equal': return availableMajor <= requestedMajor; + case 'latestPatch': + case 'latestFeature': + return false } } } @@ -213,7 +262,7 @@ Please set the PATH to a dotnet host that matches the architecture ${requirement const aspnetCoreString = 'Microsoft.AspNetCore.App'; const runtimeString = 'Microsoft.NETCore.App'; - const runtimeInfo = await (this.executor!).execute(findRuntimesCommand, {dotnetInstallToolCacheTtlMs: DOTNET_INFORMATION_CACHE_DURATION_MS}, false).then((result) => + const runtimeInfo = await (this.executor!).execute(findRuntimesCommand, { dotnetInstallToolCacheTtlMs: DOTNET_INFORMATION_CACHE_DURATION_MS }, false).then((result) => { const runtimes = result.stdout.split('\n').map((line) => line.trim()).filter((line) => line.length > 0); const runtimeInfos: IDotnetListInfo[] = runtimes.map((runtime) => diff --git a/vscode-dotnet-runtime-library/src/DotnetVersionSpecRequirement.ts b/vscode-dotnet-runtime-library/src/DotnetVersionSpecRequirement.ts index 0f463f2427..4b426157cb 100644 --- a/vscode-dotnet-runtime-library/src/DotnetVersionSpecRequirement.ts +++ b/vscode-dotnet-runtime-library/src/DotnetVersionSpecRequirement.ts @@ -7,6 +7,19 @@ * When this condition is used, the available version is compared to the required version. * For example, if the request is made looking for 8.0 and allowing 'greater_than_or_equal', then 10.0 would be accepted, * because 10.0 >= 8.0. + * + * In addition, certain values from globalJson's rollForward property are allowed. + +latestPatch - Uses the latest installed patch level that matches the requested major, minor, and feature band with a patch level that's greater than or equal to the specified value. +If not found, fails. (in other words, 200 is not ok if we want 102.) + +latestFeature Uses the highest installed feature band and patch level that matches the requested major and minor with a feature band and patch level that's greater than or equal to the specified value. +If not found, fails. (in other words, 200 is ok if we want 102, but 101 is not) + +latestMajor This is the same as greater_than_or_equal. + +disable - this is the same as equal. */ -export type DotnetVersionSpecRequirement = 'equal' | 'greater_than_or_equal' | 'less_than_or_equal'; +export type DotnetVersionSpecRequirement = 'equal' | 'greater_than_or_equal' | 'less_than_or_equal' | + 'latestPatch' | 'latestFeature' | 'latestMajor' | 'disable'; // latestMinor not implemented since minor has not been used since 3.1 diff --git a/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts b/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts index 27878e2262..c08669b90c 100644 --- a/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts +++ b/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts @@ -1757,5 +1757,5 @@ export class TestAcquireCalled extends IEvent function getDisabledTelemetryOnChance(percentIntToSend: number): { [disableTelemetryId: string]: boolean } { - return { suppressTelemetry: Math.random() * 100 < percentIntToSend }; + return { suppressTelemetry: !(Math.random() < percentIntToSend / 100) }; } \ No newline at end of file diff --git a/vscode-dotnet-runtime-library/src/test/unit/DotnetConditionValidator.test.ts b/vscode-dotnet-runtime-library/src/test/unit/DotnetConditionValidator.test.ts index 32aae9ef67..9d66cb76ca 100644 --- a/vscode-dotnet-runtime-library/src/test/unit/DotnetConditionValidator.test.ts +++ b/vscode-dotnet-runtime-library/src/test/unit/DotnetConditionValidator.test.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as chai from 'chai'; import * as lodash from 'lodash'; -import { MockCommandExecutor } from '../mocks/MockObjects'; import { DotnetConditionValidator } from '../../Acquisition/DotnetConditionValidator'; -import { getMockAcquisitionContext, getMockUtilityContext } from './TestUtility'; import { IDotnetFindPathContext } from '../../IDotnetFindPathContext'; +import { MockCommandExecutor } from '../mocks/MockObjects'; +import { getMockAcquisitionContext, getMockUtilityContext } from './TestUtility'; const assert = chai.assert; const listRuntimesResultWithEightPreviewOnly = ` @@ -29,13 +29,14 @@ const listSDKsResultWithEightFull = ` ${listSDKsResultWithEightPreviewOnly} 8.0.101 [C:\\Program Files\\dotnet\\sdk] ` -const executionResultWithListRuntimesResultWithPreviewOnly = { status : '', stdout: listRuntimesResultWithEightPreviewOnly, stderr: '' }; -const executionResultWithListRuntimesResultWithFullOnly = { status : '', stdout: listRuntimesResultWithEightFull, stderr: '' }; +const executionResultWithListRuntimesResultWithPreviewOnly = { status: '', stdout: listRuntimesResultWithEightPreviewOnly, stderr: '' }; +const executionResultWithListRuntimesResultWithFullOnly = { status: '', stdout: listRuntimesResultWithEightFull, stderr: '' }; -const executionResultWithListSDKsResultWithPreviewOnly = { status : '', stdout: listSDKsResultWithEightPreviewOnly, stderr: '' }; -const executionResultWithListSDKsResultFullSDK = { status : '', stdout: listSDKsResultWithEightFull, stderr: '' }; +const executionResultWithListSDKsResultWithPreviewOnly = { status: '', stdout: listSDKsResultWithEightPreviewOnly, stderr: '' }; +const executionResultWithListSDKsResultFullSDK = { status: '', stdout: listSDKsResultWithEightFull, stderr: '' }; -suite('DotnetConditionValidator Unit Tests', () => { +suite('DotnetConditionValidator Unit Tests', () => +{ const utilityContext = getMockUtilityContext(); const acquisitionContext = getMockAcquisitionContext('runtime', '8.0'); const mockExecutor = new MockCommandExecutor(acquisitionContext, utilityContext); @@ -44,8 +45,8 @@ suite('DotnetConditionValidator Unit Tests', () => { { const requirementWithRejectPreviews = { acquireContext: acquisitionContext.acquisitionContext, - versionSpecRequirement : 'greater_than_or_equal', - rejectPreviews : true + versionSpecRequirement: 'greater_than_or_equal', + rejectPreviews: true } as IDotnetFindPathContext const requirementAllowingPreviews = lodash.cloneDeep(requirementWithRejectPreviews); @@ -84,4 +85,119 @@ suite('DotnetConditionValidator Unit Tests', () => { meetsReq = await conditionValidator.dotnetMeetsRequirement('dotnet', requirementRejectingPreviewsSDKs); assert.isTrue(meetsReq, 'It finds non preview SDK if rejectPreviews set'); }); + + test('It does not take newer major SDK if latestPatch or feature used', async () => + { + const conditionValidator = new DotnetConditionValidator(acquisitionContext, utilityContext, mockExecutor); + const contextThatCanBeIgnoredExceptMode = lodash.cloneDeep(acquisitionContext.acquisitionContext); + contextThatCanBeIgnoredExceptMode.mode = 'sdk'; + + let isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.100', '8.0.201', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'latestPatch' }); + assert.isNotTrue(isAccepted, 'It does not take 9.0 sdk for 8.0 latestPatch'); + + isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.100', '8.0.201', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'latestFeature' }); + assert.isNotTrue(isAccepted, 'It does not take 9.0 sdk for 8.0 latestFeature'); + + isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.100', '8.0.201', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'latestMajor' }); + assert.isTrue(isAccepted, 'It does take 9.0 sdk for 8.0 latestMajor'); + }); + + test('It does not take newer major Runtime if latestPatch or feature used', async () => + { + const conditionValidator = new DotnetConditionValidator(acquisitionContext, utilityContext, mockExecutor); + const contextThatCanBeIgnoredExceptMode = lodash.cloneDeep(acquisitionContext.acquisitionContext); + contextThatCanBeIgnoredExceptMode.mode = 'aspnetcore'; + + let isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.2', '8.0.1', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'latestPatch' }); + assert.isNotTrue(isAccepted, 'It doesnt take 9.0 runtime for 8.0 latestPatch'); + + isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.2', '8.0.1', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'latestFeature' }); + assert.isNotTrue(isAccepted, 'It doesnt take 9.0 runtime for 8.0 latestFeature'); + + isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.2', '8.0.1', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'latestMajor' }); + assert.isTrue(isAccepted, 'It does take 9.0 runtime for 8.0 latestMajor'); + }); + + test('It does not take latest SDK feature band on latestPatch', async () => + { + const conditionValidator = new DotnetConditionValidator(acquisitionContext, utilityContext, mockExecutor); + const contextThatCanBeIgnoredExceptMode = lodash.cloneDeep(acquisitionContext.acquisitionContext); + contextThatCanBeIgnoredExceptMode.mode = 'sdk'; + + let isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.200', '9.0.102', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'latestPatch' }); + assert.isNotTrue(isAccepted, 'It does not take latest feature band on latestPatch'); + + isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.200', '9.0.102', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'latestFeature' }); + assert.isTrue(isAccepted, 'It does take latest feature on latestFeature'); + }); + + test('It does not take lower than patch on latestPatch or feature or band', async () => + { + const conditionValidator = new DotnetConditionValidator(acquisitionContext, utilityContext, mockExecutor); + const contextThatCanBeIgnoredExceptMode = lodash.cloneDeep(acquisitionContext.acquisitionContext); + contextThatCanBeIgnoredExceptMode.mode = 'sdk'; + + let isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.201', '9.0.202', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'latestFeature' }); + assert.isNotTrue(isAccepted, 'It does not take old sdk on latestFeature'); + + isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.201', '9.0.202', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'latestPatch' }); + assert.isNotTrue(isAccepted, 'It does not take old sdk on latestPatch'); + }); + + test('latestPatch and latestFeature work on runtime search', async () => + { + const conditionValidator = new DotnetConditionValidator(acquisitionContext, utilityContext, mockExecutor); + const contextThatCanBeIgnoredExceptMode = lodash.cloneDeep(acquisitionContext.acquisitionContext); + contextThatCanBeIgnoredExceptMode.mode = 'runtime'; + + let isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.2', '9.0.1', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'latestPatch' }); + assert.isTrue(isAccepted, 'It does not fail with latestPatch on runtime'); + + isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.2', '9.0.1', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'latestFeature' }); + assert.isTrue(isAccepted, 'It does take latest runtime patch on latestFeature'); + + isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.2', '9.0.3', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'latestFeature' }); + assert.isNotTrue(isAccepted, 'It does not take old runtime on latestFeature'); + + isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.2', '9.0.3', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'latestPatch' }); + assert.isNotTrue(isAccepted, 'It does not take old runtime on latestPatch'); + }); + + test('rollForward disable is equal to == on runtime', async () => + { + const conditionValidator = new DotnetConditionValidator(acquisitionContext, utilityContext, mockExecutor); + const contextThatCanBeIgnoredExceptMode = lodash.cloneDeep(acquisitionContext.acquisitionContext); + contextThatCanBeIgnoredExceptMode.mode = 'aspnetcore'; + + let isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.2', '9.0.1', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'disable' }); + assert.isNotTrue(isAccepted, 'disable does not allow upgrade'); + + isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.2', '9.0.3', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'disable' }); + assert.isNotTrue(isAccepted, 'disable does not allow downgrade'); + + isAccepted = conditionValidator.stringVersionMeetsRequirement('10.0.4', '9.0.3', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'disable' }); + assert.isNotTrue(isAccepted, 'disable does not allow major upgrade'); + + isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.1', '9.0.1', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'disable' }); + assert.isTrue(isAccepted, 'disable only takes the exact match runtime'); + }); + + test('rollForward disable is equal to == on sdk', async () => + { + const conditionValidator = new DotnetConditionValidator(acquisitionContext, utilityContext, mockExecutor); + const contextThatCanBeIgnoredExceptMode = lodash.cloneDeep(acquisitionContext.acquisitionContext); + contextThatCanBeIgnoredExceptMode.mode = 'sdk'; + + let isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.102', '9.0.101', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'disable' }); + assert.isNotTrue(isAccepted, 'disable does not allow upgrade on sdk patch'); + + isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.201', '9.0.101', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'disable' }); + assert.isNotTrue(isAccepted, 'disable does not allow upgraded sdk band'); + + isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.201', '9.0.300', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'disable' }); + assert.isNotTrue(isAccepted, 'disable does not allow downgrade'); + + isAccepted = conditionValidator.stringVersionMeetsRequirement('9.0.1', '9.0.1', { acquireContext: contextThatCanBeIgnoredExceptMode, versionSpecRequirement: 'disable' }); + assert.isTrue(isAccepted, 'disable only takes the exact match sdk'); + }); });