Skip to content

Commit

Permalink
feat(core): dotnet test support #20
Browse files Browse the repository at this point in the history
  • Loading branch information
AgentEnder committed Apr 28, 2021
1 parent 4eae1c6 commit 02ceed0
Show file tree
Hide file tree
Showing 10 changed files with 247 additions and 28 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
npm-debug.log
yarn-error.log
testem.log
.npmrc
/typings

# System Files
Expand Down
2 changes: 2 additions & 0 deletions .npmrc.local
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
; Set a new registry for a scoped package
@nx-dotnet:registry=http://localhost:4873
30 changes: 28 additions & 2 deletions packages/core/src/executors/test/executor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
import { ExecutorContext } from '@nrwl/devkit';
import {
DotNetClient,
dotnetFactory,
dotnetTestFlags,
} from '@nx-dotnet/dotnet';
import {
getExecutedProjectConfiguration,
getProjectFileForNxProject,
} from '@nx-dotnet/utils';
import { TestExecutorSchema } from './schema';

export default async function runExecutor(options: TestExecutorSchema) {
console.log('Executor ran for Test', options);
export default async function runExecutor(
options: TestExecutorSchema,
context: ExecutorContext,
dotnetClient: DotNetClient = new DotNetClient(dotnetFactory())
) {
const nxProjectConfiguration = getExecutedProjectConfiguration(context);
const projectFilePath = await getProjectFileForNxProject(
nxProjectConfiguration
);

dotnetClient.test(
projectFilePath,
Object.keys(options).map((x) => ({
flag: x as dotnetTestFlags,
value: (options as Record<string, string | boolean>)[x],
}))
);

return {
success: true,
};
Expand Down
35 changes: 34 additions & 1 deletion packages/core/src/executors/test/schema.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,34 @@
export interface TestExecutorSchema {} // eslint-disable-line
export interface TestExecutorSchema {
testAdapterPath?: string;
blame?: boolean;
blameCrash?: boolean;
blameCrashDumpType?: string;
blameCrashCollectAlways?: boolean;
blameHang?: boolean;
blameHangDumpType?: string;
blameHangTimeout?: string;
configuration?: string;
collect?: string;
diag?: string;
framework?: string;
filter?: string;
logger?: string;
noBuild?: boolean;
noRestore?: boolean;
output?: string;
resultsDirectory?: string;
runtime?: string;
settings?: string;
listTests?: boolean;
verbosity?:
| 'quiet'
| 'q'
| 'm'
| 'minimal'
| 'n'
| 'normal'
| 'd'
| 'detailed'
| 'diag'
| 'diagnostic';
}
107 changes: 106 additions & 1 deletion packages/core/src/executors/test/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,111 @@
"title": "Test executor",
"description": "",
"type": "object",
"properties": {},
"properties": {
"testAdapterPath": {
"description": "Path to a directory to be searched for additional test adapters. Only .dll files with suffix .TestAdapter.dll are inspected. If not specified, the directory of the test .dll is searched.",
"type": "string"
},
"blame": {
"description": "Runs the tests in blame mode. This option is helpful in isolating problematic tests that cause the test host to crash. When a crash is detected, it creates a sequence file in TestResults/<Guid>/<Guid>_Sequence.xml that captures the order of tests that were run before the crash.",
"type": "boolean",
"default": false
},
"blameCrash": {
"description": "Runs the tests in blame mode and collects a crash dump when the test host exits unexpectedly. This option depends on the version of .NET used, the type of error, and the operating system. For exceptions in managed code, a dump will be automatically collected on .NET 5.0 and later versions. It will generate a dump for testhost or any child process that also ran on .NET 5.0 and crashed. Crashes in native code will not generate a dump. This option works on Windows, macOS, and Linux. Crash dumps in native code, or when using .NET Core 3.1 or earlier versions, can only be collected on Windows, by using Procdump. A directory that contains procdump.exe and procdump64.exe must be in the PATH or PROCDUMP_PATH environment variable. Download the tools. Implies --blame. To collect a crash dump from a native application running on .NET 5.0 or later, the usage of Procdump can be forced by setting the VSTEST_DUMP_FORCEPROCDUMP environment variable to 1.",
"type": "boolean",
"default": false
},
"blameCrashDumpType": {
"description": "The type of crash dump to be collected. Implies --blame-crash.",
"type": "string"
},
"blameCrashCollectAlways": {
"description": "Collects a crash dump on expected as well as unexpected test host exit.",
"type": "boolean",
"default": false
},
"blameHang": {
"description": "Run the tests in blame mode and collects a hang dump when a test exceeds the given timeout.",
"type": "boolean",
"default": false
},
"blameHangDumpType": {
"description": "The type of crash dump to be collected. It should be full, mini, or none. When none is specified, test host is terminated on timeout, but no dump is collected. Implies --blame-hang.",
"type": "string"
},
"blameHangTimeout": {
"description": "Per-test timeout, after which a hang dump is triggered and the test host process and all of its child processes are dumped and terminated. The timeout value is specified in one of the following formats: \n 1.5h, 1.5hour, 1.5hours \n 90m, 90min, 90minute, 90minutes \n 5400s, 5400sec, 5400second, 5400seconds \n 5400000ms, 5400000mil, 5400000millisecond, 5400000milliseconds \n When no unit is used (for example, 5400000), the value is assumed to be in milliseconds. When used together with data driven tests, the timeout behavior depends on the test adapter used. For xUnit and NUnit the timeout is renewed after every test case. For MSTest, the timeout is used for all test cases. This option is supported on Windows with netcoreapp2.1 and later, on Linux with netcoreapp3.1 and later, and on macOS with net5.0 or later. Implies --blame and --blame-hang.",
"type": "string"
},
"configuration": {
"description": "Defines the build configuration. The default value is Debug, but your project's configuration could override this default SDK setting.",
"type": "string"
},
"collect": {
"description": "Enables data collector for the test run. For more information, see Monitor and analyze test run. \n To collect code coverage on any platform that is supported by .NET Core, install Coverlet and use the --collect:\"XPlat Code Coverage\" option. \n On Windows, you can collect code coverage by using the --collect \"Code Coverage\" option. This option generates a .coverage file, which can be opened in Visual Studio 2019 Enterprise. For more information, see Use code coverage and Customize code coverage analysis. ",
"type": "string"
},
"diag": {
"description": "Enables diagnostic mode for the test platform and writes diagnostic messages to the specified file and to files next to it. The process that is logging the messages determines which files are created, such as *.host_<date>.txt for test host log, and *.datacollector_<date>.txt for data collector log.",
"type": "string"
},
"framework": {
"description": "Forces the use of dotnet or .NET Framework test host for the test binaries. This option only determines which type of host to use. The actual framework version to be used is determined by the runtimeconfig.json of the test project. When not specified, the TargetFramework assembly attribute is used to determine the type of host. When that attribute is stripped from the .dll, the .NET Framework host is used.",
"type": "string"
},
"filter": {
"description": "Filters out tests in the current project using the given expression. For more information, see the Filter option details section. For more information and examples on how to use selective unit test filtering, see Running selective unit tests.",
"type": "string"
},
"logger": {
"description": "Specifies a logger for test results. Unlike MSBuild, dotnet test doesn't accept abbreviations: instead of -l \"console;v=d\" use -l \"console;verbosity=detailed\". Specify the parameter multiple times to enable multiple loggers.",
"type": "string"
},
"noBuild": {
"description": "Doesn't build the test project before running it. It also implicitly sets the - --no-restore flag.",
"type": "boolean"
},
"noRestore": {
"description": "Doesn't execute an implicit restore when running the command.",
"type": "boolean"
},
"output": {
"description": "Directory in which to find the binaries to run. If not specified, the default path is ./bin/<configuration>/<framework>/. For projects with multiple target frameworks (via the TargetFrameworks property), you also need to define --framework when you specify this option. dotnet test always runs tests from the output directory. You can use AppDomain.BaseDirectory to consume test assets in the output directory.",
"type": "string"
},
"resultsDirectory": {
"description": "The directory where the test results are going to be placed. If the specified directory doesn't exist, it's created. The default is TestResults in the directory that contains the project file.",
"type": "string"
},
"runtime": {
"description": "The target runtime to test for.",
"type": "string"
},
"settings": {
"description": "The .runsettings file to use for running the tests. The TargetPlatform element (x86|x64) has no effect for dotnet test. To run tests that target x86, install the x86 version of .NET Core. The bitness of the dotnet.exe that is on the path is what will be used for running tests. For more information, see the following resources:",
"type": "string"
},
"listTests": {
"description": "List the discovered tests instead of running the tests.",
"type": "boolean"
},
"verbosity": {
"description": "Sets the verbosity level of the command. For more information, see LoggerVerbosity.",
"type": "string",
"enum": [
"quiet",
"q",
"m",
"minimal",
"n",
"normal",
"d",
"detailed",
"diag",
"diagnostic"
]
}
},
"required": []
}
10 changes: 3 additions & 7 deletions packages/core/src/generators/utils/generate-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,17 +188,13 @@ export async function GenerateProject(
sourceRoot: `${normalizedOptions.projectRoot}`,
targets: {
build: GetBuildExecutorConfiguration(normalizedOptions.projectRoot),
...(projectType === 'application' ? { serve: GetServeExecutorConfig() } : {}),
...(projectType === 'application'
? { serve: GetServeExecutorConfig() }
: {}),
},
tags: normalizedOptions.parsedTags,
};

if (options['test-template'] !== 'none') {
projectConfiguration.targets.test = GetTestExecutorConfig(
normalizedOptions.projectName + '-test'
);
}

addProjectConfiguration(
host,
normalizedOptions.projectName,
Expand Down
18 changes: 17 additions & 1 deletion packages/dotnet/src/lib/core/dotnet.client.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { ChildProcess, execSync, spawn } from 'child_process';

import { getParameterString } from '@nx-dotnet/utils';
import {
getParameterString,
swapArrayFieldValueUsingMap,
} from '@nx-dotnet/utils';

import {
dotnetBuildOptions,
dotnetNewOptions,
dotnetRunOptions,
dotnetTemplate,
dotnetTestOptions,
testKeyMap,
} from '../models';
import { LoadedCLI } from './dotnet.factory';

Expand All @@ -32,6 +37,17 @@ export class DotNetClient {
return spawn(this.cliCommand.command, cmd.split(' '), { stdio: 'inherit' });
}

test(project: string, parameters?: dotnetTestOptions): Buffer {
let cmd = `${this.cliCommand.command} test ${project}`;
if (parameters) {
parameters = swapArrayFieldValueUsingMap(parameters, 'flag', testKeyMap);
const paramString = parameters ? getParameterString(parameters) : '';
cmd = `${cmd} ${paramString}`;
}
console.log(`Executing Command: ${cmd}`);
return this.logAndExecute(cmd);
}

addProjectReference(hostCsProj: string, targetCsProj: string): Buffer {
return this.logAndExecute(
`${this.cliCommand.command} add ${hostCsProj} reference ${targetCsProj}`
Expand Down
36 changes: 25 additions & 11 deletions packages/dotnet/src/lib/models/dotnet-test/dotnet-test-flags.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@
export type dotnetTestFlags =
| 'blame-crash-collect-always'
| 'blame-crash-dump-type'
| 'blame-crash'
| 'blame-hang-dump'
| 'blame-hang-timeout'
| 'blame-hang'
| 'blameCrashCollectAlways'
| 'blameCrashDumpType'
| 'blameCrash'
| 'blameHangDump'
| 'blameHangTimeout'
| 'blameHang'
| 'blame'
| 'collect'
| 'configuration'
| 'diag'
| 'filter'
| 'framework'
| 'list-tests'
| 'listTests'
| 'logger'
| 'no-build'
| 'no-restore'
| 'noBuild'
| 'noRestore'
| 'nologo'
| 'results-directory'
| 'resultsDirectory'
| 'settings'
| 'test-adapter-path'
| 'testAdapterPath'
| 'verbosity';

export const testKeyMap: Partial<{ [key in dotnetTestFlags]: string }> = {
blameCrashCollectAlways: 'blame-crash-collect-always',
blameCrash: 'blame-crash',
blameCrashDumpType: 'blame-crash-dump-type',
blameHangDump: 'blame-hang-dump',
blameHang: 'blame-hang',
blameHangTimeout: 'blame-hang-timeout',
listTests: 'list-tests',
noBuild: 'no-build',
noRestore: 'no-restore',
resultsDirectory: 'results-directory',
testAdapterPath: 'test-adapter-path',
};
24 changes: 24 additions & 0 deletions packages/utils/src/lib/args.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
export function isDryRun(): boolean {
return process.argv.some((x) => x === '--dry-run');
}

export function swapKeysUsingMap(
object: Record<string, unknown>,
map: Record<string, string>
): Record<string, unknown> {
return Object.fromEntries(
Object.entries(object).map(([key, value]) => [
key in map ? map[key] : key,
value,
])
);
}

export function swapArrayFieldValueUsingMap<T>(
array: T[],
field: keyof T,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
map: any
) {
return array.map((x) => ({
...x,
field: map[x[field]] ?? x[field],
}));
}
12 changes: 7 additions & 5 deletions packages/utils/src/lib/parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import { cmdLineParameter } from './models';
*/
export function getParameterString(parameters: cmdLineParameter[]): string {
return parameters.reduce((acc, current) => {
if (typeof current.value === 'boolean' && current.value) {
return acc + `--${current.flag} `;
if (typeof current.value === 'boolean' || !current.value) {
if (current.value) {
return acc + `--${current.flag} `;
} else {
return acc;
}
} else {
return (
acc + `--${current.flag} ` + (current.value ? `${current.value} ` : '')
);
return acc + `--${current.flag} ${current.value} `;
}
}, '');
}

0 comments on commit 02ceed0

Please sign in to comment.