Compare commits

...

36 Commits

Author SHA1 Message Date
dependabot[bot]
b002be5d72 Bump fantomas from 6.3.9 to 6.3.10 (#108)
* Bump WoofWare.Myriad.Plugins.Attributes from 3.1.8 to 3.1.9

Bumps WoofWare.Myriad.Plugins.Attributes from 3.1.8 to 3.1.9.

---
updated-dependencies:
- dependency-name: WoofWare.Myriad.Plugins.Attributes
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump fantomas from 6.3.9 to 6.3.10

Bumps fantomas from 6.3.9 to 6.3.10.

---
updated-dependencies:
- dependency-name: fantomas
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update lockfile

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-07-15 17:44:32 +00:00
dependabot[bot]
5483184edc Bump actions/attest-build-provenance from 1.3.2 to 1.3.3 (#106)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 1.3.2 to 1.3.3.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](bdd51370e0...5e9cb68e95)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-15 18:34:00 +01:00
patrick-conscriptus[bot]
3596b8a3a8 Upgrade Nix flake and deps (#105)
* Automated commit

* Fix flake

---------

Co-authored-by: patrick-conscriptus[bot] <175414948+patrick-conscriptus[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-07-14 07:50:39 +00:00
Patrick Stevens
68326d7628 Add flake update workflow (#103) 2024-07-12 21:52:28 +00:00
Patrick Stevens
f4b0a5457b Abstract away the required-checks feature (#104) 2024-07-12 21:58:01 +01:00
Patrick Stevens
c4b67304a1 Fix required checks (#102) 2024-07-12 17:24:12 +01:00
Patrick Stevens
337a0635d2 Add reproducibility check (#101) 2024-07-11 00:36:13 +01:00
dependabot[bot]
e8e302db2d Bump ApiSurface from 4.0.42 to 4.0.43 (#100)
* Bump ApiSurface from 4.0.42 to 4.0.43

Bumps ApiSurface from 4.0.42 to 4.0.43.

---
updated-dependencies:
- dependency-name: ApiSurface
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-07-08 18:57:55 +01:00
dependabot[bot]
1522e3cc9c Bump WoofWareMyriadPluginVersion from 2.1.51 to 2.1.52 (#99)
* Bump WoofWareMyriadPluginVersion from 2.1.51 to 2.1.52

Bumps `WoofWareMyriadPluginVersion` from 2.1.51 to 2.1.52.

Updates `WoofWare.Myriad.Plugins` from 2.1.51 to 2.1.52
- [Release notes](https://github.com/Smaug123/WoofWare.Myriad/releases)
- [Changelog](https://github.com/Smaug123/WoofWare.Myriad/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Smaug123/WoofWare.Myriad/compare/WoofWare.Myriad.Plugins.2.1.51...WoofWare.Myriad.Plugins.2.1.52)

Updates `WoofWare.Myriad.Plugins.Attributes` from 3.1.7 to 3.1.8
- [Release notes](https://github.com/Smaug123/WoofWare.Myriad/releases)
- [Changelog](https://github.com/Smaug123/WoofWare.Myriad/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Smaug123/WoofWare.Myriad/compare/WoofWare.Myriad.Plugins.Attributes.3.1.7...WoofWare.Myriad.Plugins.Attributes.3.1.8)

---
updated-dependencies:
- dependency-name: WoofWare.Myriad.Plugins
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: WoofWare.Myriad.Plugins.Attributes
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-07-01 19:05:43 +01:00
dependabot[bot]
81c6b584a4 Bump ApiSurface from 4.0.41 to 4.0.42, WoofWare.Myriad.Plugins from 2.1.45 to 2.1.51 (#98)
* Bump WoofWare.Myriad.Plugins from 2.1.45 to 2.1.49

Bumps [WoofWare.Myriad.Plugins](https://github.com/Smaug123/WoofWare.Myriad) from 2.1.45 to 2.1.49.
- [Release notes](https://github.com/Smaug123/WoofWare.Myriad/releases)
- [Changelog](https://github.com/Smaug123/WoofWare.Myriad/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Smaug123/WoofWare.Myriad/compare/WoofWare.Myriad.Plugins.2.1.45...WoofWare.Myriad.Plugins.2.1.49)

---
updated-dependencies:
- dependency-name: WoofWare.Myriad.Plugins
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump ApiSurface from 4.0.41 to 4.0.42

Bumps [ApiSurface](https://github.com/G-Research/ApiSurface) from 4.0.41 to 4.0.42.
- [Release notes](https://github.com/G-Research/ApiSurface/releases)
- [Commits](https://github.com/G-Research/ApiSurface/compare/ApiSurface.4.0.41...ApiSurface.4.0.42)

---
updated-dependencies:
- dependency-name: ApiSurface
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-07-01 18:44:01 +01:00
dependabot[bot]
40824e06e7 Bump actions/attest-build-provenance from 1.0.0 to 1.3.2 (#96)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 1.0.0 to 1.3.2.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](897ed5eab6...bdd51370e0)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-24 18:54:49 +01:00
Patrick Stevens
fb945c04ac Use DOTNET_STARTUP_HOOK to have the target run the tests (#91) 2024-06-23 17:25:24 +01:00
Patrick Stevens
85cd116d52 Delete unused file (#94) 2024-06-23 15:30:46 +01:00
Patrick Stevens
8e7c54cc83 Split out AppContext and Args to lib (#93) 2024-06-23 15:26:35 +01:00
Patrick Stevens
378a0169f8 Formatting (#92) 2024-06-23 15:13:40 +01:00
Patrick Stevens
870804d6ef Multi-framework Consumer test (#89) 2024-06-23 11:50:58 +01:00
Patrick Stevens
9f5f22c644 Pull report generation into lib (#90) 2024-06-23 11:44:53 +01:00
Patrick Stevens
296f230616 Bump deps (#88) 2024-06-17 23:51:00 +01:00
Patrick Stevens
56ac203570 Attest contents of NuGet packages (#87) 2024-06-17 23:37:53 +01:00
Patrick Stevens
e17e769d5a Direnv (#84) 2024-06-16 23:29:01 +01:00
Patrick Stevens
57c34e0c4c Permit self-contained test fixtures (#83) 2024-06-16 21:26:31 +01:00
Patrick Stevens
7f9464b826 Args (#82) 2024-06-16 19:28:16 +01:00
Patrick Stevens
3d04199c56 More runtimes (#81) 2024-06-16 16:13:58 +01:00
Patrick Stevens
9d4b893e02 Run tests in parallel (#79) 2024-06-16 15:43:07 +01:00
Patrick Stevens
55e9645316 Rewrite tests to allow being run in parallel (#80) 2024-06-16 15:20:07 +01:00
Patrick Stevens
e9dc768449 Parallelize (#69) 2024-06-16 11:55:10 +01:00
Patrick Stevens
e0b2d52812 Generalise Spectre to arbitrary console (#78) 2024-06-15 23:57:48 +01:00
Patrick Stevens
2ed4a04f70 Add test for stdout/stderr redirection (#77) 2024-06-15 23:32:31 +01:00
Patrick Stevens
2e066a1a9a Recognise parallelism attributes (#76) 2024-06-15 23:26:19 +01:00
Patrick Stevens
6468a301b9 Capture stderr too (#75) 2024-06-15 23:06:11 +01:00
Patrick Stevens
13f636df3d Promote runtime stuff to lib (#74) 2024-06-15 22:55:30 +01:00
Patrick Stevens
eed076cad5 Cap runtime of test (#73) 2024-06-15 22:50:15 +01:00
Patrick Stevens
df64e46079 Cope with parameterised fixtures (#70) 2024-06-11 23:28:13 +01:00
Patrick Stevens
b3bc0aa4c0 Fix param count error with TestCase (#68) 2024-06-11 22:41:43 +01:00
Patrick Stevens
d3f9ee6b02 Throw on SetUpFixture (#63) 2024-06-10 23:45:03 +01:00
Patrick Stevens
3e6fff27d6 Stop throwing on unrecognised exceptions (#62) 2024-06-10 23:28:49 +01:00
50 changed files with 2630 additions and 741 deletions

View File

@@ -3,7 +3,7 @@
"isRoot": true, "isRoot": true,
"tools": { "tools": {
"fantomas": { "fantomas": {
"version": "6.3.9", "version": "6.3.10",
"commands": [ "commands": [
"fantomas" "fantomas"
] ]

View File

@@ -1,40 +1,40 @@
root=true root = true
[*] [*]
charset=utf-8 charset = utf-8
trim_trailing_whitespace=true trim_trailing_whitespace = true
insert_final_newline=true insert_final_newline = true
indent_style=space indent_style = space
indent_size=4 indent_size = 4
# ReSharper properties # ReSharper properties
resharper_xml_indent_size=2 resharper_xml_indent_size = 2
resharper_xml_max_line_length=100 resharper_xml_max_line_length = 100
resharper_xml_tab_width=2 resharper_xml_tab_width = 2
[*.{csproj,fsproj,sqlproj,targets,props,ts,tsx,css,json}] [*.{csproj,fsproj,sqlproj,targets,props,ts,tsx,css,json}]
indent_style=space indent_style = space
indent_size=2 indent_size = 2
[*.{fs,fsi}] [*.{fs,fsi}]
fsharp_bar_before_discriminated_union_declaration=true fsharp_bar_before_discriminated_union_declaration = true
fsharp_space_before_uppercase_invocation=true fsharp_space_before_uppercase_invocation = true
fsharp_space_before_class_constructor=true fsharp_space_before_class_constructor = true
fsharp_space_before_member=true fsharp_space_before_member = true
fsharp_space_before_colon=true fsharp_space_before_colon = true
fsharp_space_before_semicolon=true fsharp_space_before_semicolon = true
fsharp_multiline_bracket_style=aligned fsharp_multiline_bracket_style = aligned
fsharp_newline_between_type_definition_and_members=true fsharp_newline_between_type_definition_and_members = true
fsharp_align_function_signature_to_indentation=true fsharp_align_function_signature_to_indentation = true
fsharp_alternative_long_member_definitions=true fsharp_alternative_long_member_definitions = true
fsharp_multi_line_lambda_closing_newline=true fsharp_multi_line_lambda_closing_newline = true
fsharp_experimental_keep_indent_in_branch=true fsharp_experimental_keep_indent_in_branch = true
fsharp_max_value_binding_width=80 fsharp_max_value_binding_width = 80
fsharp_max_record_width=0 fsharp_max_record_width = 0
max_line_length=120 max_line_length = 120
end_of_line=lf end_of_line = lf
[*.{appxmanifest,build,dtd,nuspec,xaml,xamlx,xoml,xsd}] [*.{appxmanifest,build,dtd,nuspec,xaml,xamlx,xoml,xsd}]
indent_style=space indent_style = space
indent_size=2 indent_size = 2
tab_width=2 tab_width = 2

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

14
.github/workflows/assert-contents.sh vendored Normal file
View File

@@ -0,0 +1,14 @@
#!/bin/bash
echo "Unzipping version from NuGet"
ls from-nuget.nupkg
mkdir from-nuget && cp from-nuget.nupkg from-nuget/zip.zip && cd from-nuget && unzip zip.zip && rm zip.zip && cd - || exit 1
echo "Unzipping version from local build"
ls packed/
mkdir from-local && cp packed/*.nupkg from-local/zip.zip && cd from-local && unzip zip.zip && rm zip.zip && cd - || exit 1
cd from-local && find . -type f -exec sha256sum {} \; | sort > ../from-local.txt && cd .. || exit 1
cd from-nuget && find . -type f -and -not -name '.signature.p7s' -exec sha256sum {} \; | sort > ../from-nuget.txt && cd .. || exit 1
diff from-local.txt from-nuget.txt

View File

@@ -38,7 +38,7 @@ jobs:
- name: Build - name: Build
run: 'nix develop --command dotnet build --no-restore --configuration ${{matrix.config}}' run: 'nix develop --command dotnet build --no-restore --configuration ${{matrix.config}}'
- name: Test - name: Test
run: 'nix develop --command dotnet test --no-build --verbosity normal --configuration ${{matrix.config}}' run: 'nix develop --command dotnet test --no-build --verbosity normal --configuration ${{matrix.config}} --framework net8.0'
selftest: selftest:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -71,7 +71,49 @@ jobs:
- name: Build - name: Build
run: 'nix develop --command dotnet build --no-restore --configuration Release' run: 'nix develop --command dotnet build --no-restore --configuration Release'
- name: Test using self - name: Test using self
run: 'nix develop --command dotnet exec ./WoofWare.NUnitTestRunner/bin/Release/net8.0/WoofWare.NUnitTestRunner.dll ./Consumer/bin/Release/net8.0/Consumer.dll --trx TrxOut/out.trx' run: 'nix develop --command dotnet exec ./WoofWare.NUnitTestRunner/bin/Release/net6.0/WoofWare.NUnitTestRunner.dll ./Consumer/bin/Release/net8.0/Consumer.dll --trx TrxOut/out.trx'
- name: Parse Trx files
uses: NasAmin/trx-parser@v0.6.0
if: always()
id: trx-parser
with:
TRX_PATH: ${{ github.workspace }}/TrxOut
REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
selftest-net6:
runs-on: ubuntu-latest
permissions:
actions: none
checks: write
contents: read
deployments: none
id-token: none
issues: none
discussions: none
packages: none
pages: none
pull-requests: read
repository-projects: none
security-events: none
statuses: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # so that NerdBank.GitVersioning has access to history
- name: Install Nix
uses: cachix/install-nix-action@V27
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- name: Restore dependencies
run: nix develop --command dotnet restore
- name: Build runner
run: 'nix develop --command dotnet build WoofWare.NUnitTestRunner --configuration Release'
- name: Build target
run: 'nix develop --command dotnet build Consumer --configuration Release'
- name: Test using self
run: 'nix develop .#net6 --command ./WoofWare.NUnitTestRunner/bin/Release/net6.0/WoofWare.NUnitTestRunner ./Consumer/bin/Release/net6.0/Consumer.dll --trx TrxOut/out.trx'
- name: Parse Trx files - name: Parse Trx files
uses: NasAmin/trx-parser@v0.6.0 uses: NasAmin/trx-parser@v0.6.0
if: always() if: always()
@@ -113,6 +155,8 @@ jobs:
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- name: Build - name: Build
run: nix build run: nix build
- name: Reproducibility check
run: nix build --rebuild
check-dotnet-format: check-dotnet-format:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -235,16 +279,61 @@ jobs:
run: sh .github/workflows/tag.sh run: sh .github/workflows/tag.sh
all-required-checks-complete: all-required-checks-complete:
if: ${{ always() }}
needs: [check-dotnet-format, check-nix-format, build, build-nix, linkcheck, flake-check, analyzers, nuget-pack, expected-pack, github-release-tool-dry-run] needs: [check-dotnet-format, check-nix-format, build, build-nix, linkcheck, flake-check, analyzers, nuget-pack, expected-pack, github-release-tool-dry-run]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- run: echo "All required checks complete." - uses: Smaug123/all-required-checks-complete-action@05b40a8c47ef0b175ea326e9abb09802cb67b44e
with:
needs-context: ${{ toJSON(needs) }}
nuget-publish: attestation-lib:
runs-on: ubuntu-latest
needs: [all-required-checks-complete]
if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
permissions:
id-token: write
attestations: write
contents: read
steps:
- name: Download NuGet artifact
uses: actions/download-artifact@v4
with:
name: nuget-package-lib
path: packed
- name: Attest Build Provenance
uses: actions/attest-build-provenance@5e9cb68e95676991667494a6a4e59b8a2f13e1d0 # v1.3.3
with:
subject-path: "packed/*.nupkg"
attestation-tool:
runs-on: ubuntu-latest
needs: [all-required-checks-complete]
if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
permissions:
id-token: write
attestations: write
contents: read
steps:
- name: Download NuGet artifact
uses: actions/download-artifact@v4
with:
name: nuget-package-tool
path: packed
- name: Attest Build Provenance
uses: actions/attest-build-provenance@5e9cb68e95676991667494a6a4e59b8a2f13e1d0 # v1.3.3
with:
subject-path: "packed/*.nupkg"
nuget-publish-lib:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }} if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
needs: [all-required-checks-complete] needs: [all-required-checks-complete]
environment: main-deploy environment: main-deploy
permissions:
id-token: write
attestations: write
contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install Nix - name: Install Nix
@@ -252,20 +341,73 @@ jobs:
with: with:
extra_nix_config: | extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- name: Download NuGet artifact (lib) - name: Download NuGet artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: nuget-package-lib name: nuget-package-lib
path: packed-lib path: packed
- name: Publish to NuGet (lib) - name: Publish to NuGet
run: nix develop --command dotnet nuget push "packed-lib/WoofWare.NUnitTestRunner.Lib.*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate id: publish-success
- name: Download NuGet artifact (tool) env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
run: 'nix develop --command bash ./.github/workflows/nuget-push.sh "packed/WoofWare.NUnitTestRunner.Lib.*.nupkg"'
- name: Wait for availability
if: steps.publish-success.outputs.result == 'published'
env:
PACKAGE_VERSION: ${{ steps.publish-success.outputs.version }}
run: 'echo "$PACKAGE_VERSION" && while ! curl -L --fail -o from-nuget.nupkg "https://www.nuget.org/api/v2/package/WoofWare.NUnitTestRunner.Lib/$PACKAGE_VERSION" ; do sleep 10; done'
# Astonishingly, NuGet.org considers it to be "more secure" to tamper with my package after upload (https://devblogs.microsoft.com/nuget/introducing-repository-signatures/).
# So we have to *re-attest* it after it's uploaded. Mind-blowing.
- name: Assert package contents
if: steps.publish-success.outputs.result == 'published'
run: 'bash ./.github/workflows/assert-contents.sh'
- name: Attest Build Provenance
if: steps.publish-success.outputs.result == 'published'
uses: actions/attest-build-provenance@5e9cb68e95676991667494a6a4e59b8a2f13e1d0 # v1.3.3
with:
subject-path: "from-nuget.nupkg"
nuget-publish-tool:
runs-on: ubuntu-latest
if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }}
needs: [all-required-checks-complete]
environment: main-deploy
permissions:
id-token: write
attestations: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@V27
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- name: Download NuGet artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: nuget-package-tool name: nuget-package-tool
path: packed-tool path: packed
- name: Publish to NuGet (tool) - name: Publish to NuGet
run: nix develop --command dotnet nuget push "packed-tool/WoofWare.NUnitTestRunner.*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate id: publish-success
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
run: 'nix develop --command bash ./.github/workflows/nuget-push.sh "packed/WoofWare.NUnitTestRunner.*.nupkg"'
- name: Wait for availability
if: steps.publish-success.outputs.result == 'published'
env:
PACKAGE_VERSION: ${{ steps.publish-success.outputs.version }}
run: 'echo "$PACKAGE_VERSION" && while ! curl -L --fail -o from-nuget.nupkg "https://www.nuget.org/api/v2/package/WoofWare.NUnitTestRunner/$PACKAGE_VERSION" ; do sleep 10; done'
# Astonishingly, NuGet.org considers it to be "more secure" to tamper with my package after upload (https://devblogs.microsoft.com/nuget/introducing-repository-signatures/).
# So we have to *re-attest* it after it's uploaded. Mind-blowing.
- name: Assert package contents
if: steps.publish-success.outputs.result == 'published'
run: 'bash ./.github/workflows/assert-contents.sh'
- name: Attest Build Provenance
if: steps.publish-success.outputs.result == 'published'
uses: actions/attest-build-provenance@5e9cb68e95676991667494a6a4e59b8a2f13e1d0 # v1.3.3
with:
subject-path: "from-nuget.nupkg"
github-release-tool: github-release-tool:
runs-on: ubuntu-latest runs-on: ubuntu-latest

57
.github/workflows/flake_update.yaml vendored Normal file
View File

@@ -0,0 +1,57 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/github-workflow.json
name: Weekly Nix Flake Update
on:
schedule:
- cron: '0 0 * * 0' # Runs at 00:00 every Sunday
workflow_dispatch: # Allows manual triggering
jobs:
update-nix-flake:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Update Nix flake
run: 'nix flake update'
- name: Build passthru
run: 'nix build ".#default.passthru.fetch-deps"'
- name: Run passthru
run: |
set -o pipefail
./result | tee /tmp/passthru.txt
cp /"$(cat /tmp/passthru.txt | grep " wrote lockfile to " | cut -d / -f 2-)" nix/deps.nix
- name: Format
run: 'nix develop --command alejandra .'
- name: Create token
id: generate-token
uses: actions/create-github-app-token@v1
with:
# https://github.com/actions/create-github-app-token/issues/136
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Raise pull request
uses: Smaug123/commit-action@cc25e6d80a796c49669dda4a0aa36c54c573983d
id: cpr
with:
bearer-token: ${{ steps.generate-token.outputs.token }}
pr-title: "Upgrade Nix flake and deps"
- name: Enable Pull Request Automerge
if: ${{ steps.cpr.outputs.pull-request-number }}
uses: peter-evans/enable-pull-request-automerge@v3
with:
token: ${{ steps.generate-token.outputs.token }}
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
merge-method: squash

24
.github/workflows/nuget-push.sh vendored Normal file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
SOURCE_NUPKG=$(find . -type f -name '*.nupkg')
PACKAGE_VERSION=$(basename "$SOURCE_NUPKG" | rev | cut -d '.' -f 2-4 | rev)
echo "version=$PACKAGE_VERSION" >> "$GITHUB_OUTPUT"
tmp=$(mktemp)
if ! dotnet nuget push "$SOURCE_NUPKG" --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json > "$tmp" ; then
cat "$tmp"
if grep 'already exists and cannot be modified' "$tmp" ; then
echo "result=skipped" >> "$GITHUB_OUTPUT"
exit 0
else
echo "Unexpected failure to upload"
exit 1
fi
fi
cat "$tmp"
echo "result=published" >> "$GITHUB_OUTPUT"

View File

@@ -1,8 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject> <IsTestProject>true</IsTestProject>
</PropertyGroup> </PropertyGroup>
@@ -10,6 +9,11 @@
<ItemGroup> <ItemGroup>
<Compile Include="NoAttribute.fs" /> <Compile Include="NoAttribute.fs" />
<Compile Include="Inconclusive.fs" /> <Compile Include="Inconclusive.fs" />
<Compile Include="RunSubProcess.fs" />
<Compile Include="TestNonParallel.fs" />
<Compile Include="TestParallel.fs" />
<Compile Include="TestStdout.fs" />
<Compile Include="TestParameterisedFixture.fs" />
<Compile Include="TestSetUp.fs" /> <Compile Include="TestSetUp.fs" />
<Compile Include="TestValues.fs" /> <Compile Include="TestValues.fs" />
<None Include="some-config.json"> <None Include="some-config.json">
@@ -22,9 +26,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="FsUnit" Version="6.0.0" /> <PackageReference Include="FsUnit" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0"/> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0"/>
<PackageReference Include="NUnit" Version="4.1.0"/> <PackageReference Include="NUnit" Version="4.1.0"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/> <PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

45
Consumer/RunSubProcess.fs Normal file
View File

@@ -0,0 +1,45 @@
namespace Consumer
open System
open System.Diagnostics
open System.IO
open System.IO.Compression
open System.Text
open NUnit.Framework
open FsUnitTyped
[<TestFixture>]
module RunSubProcess =
[<Test>]
let ``Run a subprocess`` () =
let exe = "/bin/bash"
let args = [ "-c" ; "echo hi >&2 && echo bye" ]
let workingDir = None
let psi =
ProcessStartInfo (
exe,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
WorkingDirectory = Option.toObj workingDir
)
for arg in args do
psi.ArgumentList.Add arg
psi.EnvironmentVariables.Add ("THING", Path.Combine (AppDomain.CurrentDomain.BaseDirectory, "hi"))
let stderr = StringBuilder ()
use proc = new Process (StartInfo = psi)
proc.OutputDataReceived.Add (fun e -> printfn $"%s{e.Data}")
proc.ErrorDataReceived.Add (fun e ->
eprintfn $"%s{e.Data}"
stderr.AppendLine e.Data |> ignore
)
proc.Start () |> shouldEqual true
proc.BeginOutputReadLine ()
proc.BeginErrorReadLine ()
proc.WaitForExit ()

View File

@@ -35,6 +35,13 @@ module TestCaseData =
[<TestCaseSource(nameof optionalRaw)>] [<TestCaseSource(nameof optionalRaw)>]
let ``Consume options, raw`` (s : string option) : unit = s |> shouldEqual s let ``Consume options, raw`` (s : string option) : unit = s |> shouldEqual s
[<TestCase(30, 15, 44, false)>]
let bug66 (i : int, j : int, k : int, l : bool) =
i |> shouldEqual 30
j |> shouldEqual 15
k |> shouldEqual 44
l |> shouldEqual false
[<OneTimeTearDown>] [<OneTimeTearDown>]
let tearDown () = let tearDown () =
testCasesSeen testCasesSeen

View File

@@ -0,0 +1,19 @@
namespace Consumer
open System
open System.Threading
open NUnit.Framework
open FsUnitTyped
[<TestFixture>]
[<NonParallelizable>]
module TestNonParallel =
let defaults = List.init 40 id
let lock = ref 0
[<TestCaseSource(nameof defaults)>]
let ``Default thing, but not parallel`` (i : int) =
Interlocked.Increment lock |> shouldEqual 1
Thread.Sleep (TimeSpan.FromMilliseconds (float i))
lock.Value <- 0
i |> shouldEqual i

66
Consumer/TestParallel.fs Normal file
View File

@@ -0,0 +1,66 @@
namespace Consumer
open System
open System.Threading
open NUnit.Framework
open FsUnitTyped
[<TestFixture>]
[<Parallelizable>]
module TestParallelDefault =
let defaults = List.init 60 id
[<TestCaseSource(nameof defaults)>]
let ``Default thing, no scope`` (i : int) =
Console.WriteLine i
Thread.Sleep (TimeSpan.FromMilliseconds (float i))
i |> shouldEqual i
[<TestFixture>]
[<Parallelizable(ParallelScope.All)>]
module TestParallelAllScope =
let defaults = List.init 60 id
[<TestCaseSource(nameof defaults)>]
let ``Thing, all scope`` (i : int) =
Console.WriteLine i
Thread.Sleep (TimeSpan.FromMilliseconds (float i))
i |> shouldEqual i
[<TestFixture>]
[<Parallelizable(ParallelScope.Self)>]
module TestParallelSelfScope =
let defaults = List.init 60 id
[<TestCaseSource(nameof defaults)>]
let ``Thing, self scope`` (i : int) =
Console.WriteLine i
Thread.Sleep (TimeSpan.FromMilliseconds (float i))
i |> shouldEqual i
[<TestFixture>]
[<Parallelizable(ParallelScope.Children)>]
module TestParallelChildrenScope =
let defaults = List.init 60 id
[<TestCaseSource(nameof defaults)>]
let ``Thing, children scope`` (i : int) =
Console.WriteLine i
Thread.Sleep (TimeSpan.FromMilliseconds (float i))
i |> shouldEqual i
[<TestFixture>]
[<Parallelizable(ParallelScope.Fixtures)>]
module TestParallelFixturesScope =
let defaults = List.init 60 id
[<TestCaseSource(nameof defaults)>]
let ``Thing, fixtures scope`` (i : int) =
Console.WriteLine i
Thread.Sleep (TimeSpan.FromMilliseconds (float i))
i |> shouldEqual i

View File

@@ -0,0 +1,18 @@
namespace Consumer
open NUnit.Framework
open FsUnitTyped
[<TestFixture true>]
[<TestFixture false>]
type TestParameterisedFixture (v : bool) =
[<Test>]
member _.Thing () = v |> shouldEqual v
[<TestFixture(3, true)>]
[<TestFixture(6, false)>]
type TestParameterisedFixtureMultiple (i : int, v : bool) =
[<Test>]
member _.Thing () =
v |> shouldEqual v
i |> shouldEqual i

View File

@@ -1,5 +1,6 @@
namespace Consumer namespace Consumer
open System
open FsUnitTyped open FsUnitTyped
open System.Threading open System.Threading
open NUnit.Framework open NUnit.Framework
@@ -11,6 +12,8 @@ module TestSetUp =
[<OneTimeSetUp>] [<OneTimeSetUp>]
let oneTimeSetUp () = let oneTimeSetUp () =
Console.WriteLine "I'm being set up for the first time!"
if Interlocked.Increment haveOneTimeSetUp <> 1 then if Interlocked.Increment haveOneTimeSetUp <> 1 then
failwith "one time setup happened more than once" failwith "one time setup happened more than once"
@@ -22,12 +25,14 @@ module TestSetUp =
[<SetUp>] [<SetUp>]
let setUp () = let setUp () =
Console.WriteLine "It's a set-up!"
haveOneTimeSetUp.Value |> shouldEqual 1 haveOneTimeSetUp.Value |> shouldEqual 1
let newId = Interlocked.Increment setUpTimes let newId = Interlocked.Increment setUpTimes
lock setUpTimesSeen (fun () -> setUpTimesSeen.Add newId) lock setUpTimesSeen (fun () -> setUpTimesSeen.Add newId)
[<TearDown>] [<TearDown>]
let tearDown () = let tearDown () =
Console.WriteLine "I'm a tear-down!"
let newId = Interlocked.Increment tearDownTimes let newId = Interlocked.Increment tearDownTimes
lock tearDownTimesSeen (fun () -> tearDownTimesSeen.Add newId) lock tearDownTimesSeen (fun () -> tearDownTimesSeen.Add newId)
@@ -35,19 +40,23 @@ module TestSetUp =
[<OneTimeTearDown>] [<OneTimeTearDown>]
let oneTimeTearDown () = let oneTimeTearDown () =
Console.WriteLine "I'm being torn down, finally!"
if Interlocked.Increment haveOneTimeTearDown <> 1 then if Interlocked.Increment haveOneTimeTearDown <> 1 then
failwith "one time tear down happened more than once" failwith "one time tear down happened more than once"
setUpTimesSeen setUpTimesSeen
|> Seq.toList |> Seq.toList
|> List.sort
// Six tests: one for Test, two for the TestCase, three for the Repeat. // Six tests: one for Test, two for the TestCase, three for the Repeat.
|> shouldEqual [ 1..6 ] |> shouldEqual [ 1..6 ]
tearDownTimesSeen |> Seq.toList |> shouldEqual [ 1..6 ] tearDownTimesSeen |> Seq.toList |> List.sort |> shouldEqual [ 1..6 ]
[<Test>] [<Test>]
let ``Test 1`` () = let ``Test 1`` () =
haveOneTimeTearDown.Value |> shouldEqual 0 haveOneTimeTearDown.Value |> shouldEqual 0
Console.WriteLine "By the way, I'm test 1"
1 |> shouldEqual 1 1 |> shouldEqual 1
[<TestCase "h">] [<TestCase "h">]

13
Consumer/TestStdout.fs Normal file
View File

@@ -0,0 +1,13 @@
namespace Consumer
open System
open NUnit.Framework
[<TestFixture>]
module TestStdout =
[<Test>]
let ``Stdout is redirected`` () =
Console.Out.WriteLine "Hi!"
Console.WriteLine "Hi! part 2"
Console.Error.WriteLine "Bye!"

View File

@@ -86,14 +86,42 @@ module TestValues =
[<OneTimeTearDown>] [<OneTimeTearDown>]
let ``Values are all OK`` () = let ``Values are all OK`` () =
seen1 |> Seq.toList |> shouldEqual [ true ; false ] seen1 |> Seq.toList |> List.sort |> shouldEqual [ false ; true ]
seen2 |> Seq.toList |> shouldEqual [ (true, false) ; (false, true) ]
seen3 |> Seq.toList |> shouldEqual [ (88, box 29) ; (31, box 0) ] seen2
seen4 |> Seq.toList |> shouldEqual [ ("hi", box "ohh") ; ("bye", null) ] |> Seq.toList
seen5 |> Seq.toList |> shouldEqual [ (88, box 29) ; (31, box 29) ] |> List.sort
seen6 |> Seq.toList |> shouldEqual [ ("hi", box "ohh") ; ("bye", box "ohh") ] |> shouldEqual [ (false, true) ; (true, false) ]
seen7 |> Seq.toList |> shouldEqual [ (88, box 29) ; (31, box 29) ]
seen8 |> Seq.toList |> shouldEqual [ ("hi", box "ohh") ; ("bye", box "ohh") ] seen3
|> Seq.toList
|> List.sortBy fst
|> shouldEqual [ (31, box 0) ; (88, box 29) ]
seen4
|> Seq.toList
|> List.sortBy fst
|> shouldEqual [ ("bye", null) ; ("hi", box "ohh") ]
seen5
|> Seq.toList
|> List.sortBy fst
|> shouldEqual [ (31, box 29) ; (88, box 29) ]
seen6
|> Seq.toList
|> List.sortBy fst
|> shouldEqual [ ("bye", box "ohh") ; ("hi", box "ohh") ]
seen7
|> Seq.toList
|> List.sortBy fst
|> shouldEqual [ (31, box 29) ; (88, box 29) ]
seen8
|> Seq.toList
|> List.sortBy fst
|> shouldEqual [ ("bye", box "ohh") ; ("hi", box "ohh") ]
seen9 seen9
|> Seq.toList |> Seq.toList

View File

@@ -8,3 +8,9 @@ To supply special characters in a string, XML-encode them and `"quote"` the stri
We support at least the [documented `dotnet test` examples](https://learn.microsoft.com/en-us/dotnet/core/testing/selective-unit-tests). We support at least the [documented `dotnet test` examples](https://learn.microsoft.com/en-us/dotnet/core/testing/selective-unit-tests).
However, we would recommend phrasing some of them differently, for maximum peace of mind: However, we would recommend phrasing some of them differently, for maximum peace of mind:
* `FullyQualifiedName=MyNamespace.MyTestsClass<ParameterType1%2CParameterType2>.MyTestMethod`. This would be better phrased with quotes and escaping as `FullyQualifiedName="MyNamespace.MyTestsClass&lt;ParameterType1%2CParameterType2&gt;.MyTestMethod"` * `FullyQualifiedName=MyNamespace.MyTestsClass<ParameterType1%2CParameterType2>.MyTestMethod`. This would be better phrased with quotes and escaping as `FullyQualifiedName="MyNamespace.MyTestsClass&lt;ParameterType1%2CParameterType2&gt;.MyTestMethod"`
## Parallelism
WoofWare.NUnitTestRunner has *limited* support for parallelism.
By default, we run tests serially; we may or may not respect the NUnit parallelism attributes to any given extent (but we will never incorrectly run tests in parallel).
For example, as of this writing, we do not run any tests in parallel (but the internal infrastructure is set up so that we will be able to do this soon).

View File

@@ -0,0 +1,99 @@
namespace WoofWare.NUnitTestRunner
open System
open System.IO
[<AutoOpen>]
module internal Patterns =
let (|Key|_|) (start : string) (s : string) : string option =
if s.StartsWith (start + "=", StringComparison.Ordinal) then
s.Substring (start.Length + 1) |> Some
else
None
/// Represents how verbose the test runner's logging should be.
[<RequireQualifiedAccess>]
type LogLevel =
/// Don't log any information about the test run.
| Nothing
/// Log as much information as is available about the test run.
| Verbose
/// Arguments controlling the test runner itself (not the tests therein).
type Args =
{
/// The DLL containing the tests we'll reflectively discover and invoke.
Dll : FileInfo
/// If set, the output file into which we will write a TRX report. (We'll create parent directories as necessary.)
Trx : FileInfo option
/// Also contains the original string which specified the filter.
Filter : (string * Filter) option
/// How verbose to be with the test runner's own logging.
Logging : LogLevel
/// Maximum number of tests which can run concurrently. This setting overrides any LevelOfParallelism reflectively
/// extracted from the assembly under test.
LevelOfParallelism : int option
/// Abort if the test runner is running for longer than this timeout.
Timeout : TimeSpan option
}
/// Parse `argv` into a structured Args.
static member Parse (args : string list) : Args =
match args with
| [] -> failwith "The first arg must be a positional arg, the DLL to test."
| dll :: args ->
let rec go
(trx : FileInfo option)
(filter : (string * Filter) option)
(logging : LogLevel option)
(par : int option)
(timeout : TimeSpan option)
(args : string list)
=
match args with
| [] ->
{
Dll = FileInfo dll
Trx = trx
Filter = filter
Logging = logging |> Option.defaultValue LogLevel.Nothing
LevelOfParallelism = par
Timeout = timeout
}
| Key "--filter" filterStr :: rest
| "--filter" :: filterStr :: rest ->
match filter with
| Some _ -> failwith "Two conflicting filters; you can only specify --filter once"
| None -> go trx (Some (filterStr, Filter.parse filterStr)) logging par timeout rest
| Key "--trx" trxStr :: rest
| "--trx" :: trxStr :: rest ->
match trx with
| Some _ -> failwith "Two conflicting TRX outputs; you can only specify --trx once"
| None -> go (Some (FileInfo trxStr)) filter logging par timeout rest
| Key "--verbose" verboseStr :: rest
| "--verbose" :: verboseStr :: rest ->
match logging with
| Some _ -> failwith "Two conflicting --verbose outputs; you can only specify --verbose once"
| None ->
let verbose =
if Boolean.Parse verboseStr then
LogLevel.Verbose
else
LogLevel.Nothing
go trx filter (Some verbose) par timeout rest
| Key "--parallelism" parStr :: rest
| "--parallelism" :: parStr :: rest ->
match par with
| Some _ -> failwith "Two conflicting --parallelism outputs; you can only specify --parallelism once"
| None -> go trx filter logging (Some (Int32.Parse parStr)) timeout rest
| Key "--timeout-seconds" timeoutStr :: rest
| "--timeout-seconds" :: timeoutStr :: rest ->
match timeout with
| Some _ ->
failwith "Two conflicting --timeout-seconds outputs; you can only specify --timeout-seconds once"
| None -> go trx filter logging par (Some (TimeSpan.FromSeconds (Int32.Parse timeoutStr |> float))) rest
| k :: _rest -> failwith $"Unrecognised arg %s{k}"
go None None None None None args

View File

@@ -0,0 +1,58 @@
namespace WoofWare.NUnitTestRunner
open System.Reflection
/// Attributes at the assembly level which control the behaviour of NUnit.
type AssemblyLevelAttributes =
{
/// How many tests can be running at once, if anything's running in parallel.
Parallelism : int option
/// Whether the tests in this assembly can be parallelised at all.
Parallelizable : Parallelizable<AssemblyParallelScope> option
}
[<RequireQualifiedAccess>]
module AssemblyLevelAttributes =
/// Reflectively obtain the values of any relevant assembly attributes.
let get (assy : Assembly) : AssemblyLevelAttributes =
((None, None), assy.CustomAttributes)
||> Seq.fold (fun (levelPar, par) attr ->
match attr.AttributeType.FullName with
| "NUnit.Framework.LevelOfParallelismAttribute" ->
let arg = attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox<int>
match levelPar with
| None -> (Some arg, par)
| Some existing ->
failwith $"Assembly %s{assy.Location} declares parallelism %i{arg} and also %i{existing}"
| "NUnit.Framework.NonParallelizableAttribute" ->
match levelPar with
| None -> (Some 1, par)
| Some existing ->
failwith
$"Assembly %s{assy.Location} declares non-parallelizable and also parallelism %i{existing}"
| "NUnit.Framework.ParallelizableAttribute" ->
match par with
| Some _ -> failwith "Got multiple Parallelize attributes in assembly"
| None ->
match attr.ConstructorArguments |> Seq.toList with
| [] -> levelPar, Some (Parallelizable.Yes AssemblyParallelScope.Fixtures)
| [ v ] ->
match v.Value with
| :? int as v ->
match v with
| 512 -> levelPar, Some (Parallelizable.Yes AssemblyParallelScope.Fixtures)
| 256 -> levelPar, Some (Parallelizable.Yes AssemblyParallelScope.Children)
| 257 -> failwith "ParallelScope.All is invalid on assemblies; only Fixtures or Children"
| 1 -> failwith "ParallelScope.Self is invalid on assemblies; only Fixtures or Children"
| v -> failwith $"Could not recognise value %i{v} of parallel scope on assembly"
| v -> failwith $"Unexpectedly non-int value %O{v} of parallel scope on assembly"
| _ -> failwith "unexpectedly got multiple args to Parallelizable on assembly"
| _ -> levelPar, par
)
|> fun (par, canPar) ->
{
Parallelizable = canPar
Parallelism = par
}

View File

@@ -0,0 +1,185 @@
namespace WoofWare.NUnitTestRunner
open System
open System.Collections.Generic
open System.IO
open System.Reflection
open System.Runtime.Loader
open System.Text
open System.Threading
type internal OutputStreamId = | OutputStreamId of Guid
type private ThreadAwareWriter
(
local : AsyncLocal<OutputStreamId>,
underlying : Dictionary<OutputStreamId, TextWriter>,
mem : Dictionary<OutputStreamId, MemoryStream>
)
=
inherit TextWriter ()
override _.get_Encoding () = Encoding.Default
override this.Write (v : char) : unit =
use prev = ExecutionContext.Capture ()
(fun _ ->
(fun () ->
match underlying.TryGetValue local.Value with
| true, output -> output.Write v
| false, _ ->
let wanted =
underlying |> Seq.map (fun (KeyValue (a, b)) -> $"%O{a}") |> String.concat "\n"
failwith $"no such context: %O{local.Value}\nwanted:\n"
)
|> lock underlying
)
|> fun action -> ExecutionContext.Run (prev, action, ())
override this.WriteLine (v : string) : unit =
use prev = ExecutionContext.Capture ()
(fun _ ->
(fun () ->
match underlying.TryGetValue local.Value with
| true, output -> output.WriteLine v
| false, _ ->
let wanted =
underlying |> Seq.map (fun (KeyValue (a, b)) -> $"%O{a}") |> String.concat "\n"
failwith $"no such context: %O{local.Value}\nwanted:\n"
)
|> lock underlying
)
|> fun action -> ExecutionContext.Run (prev, action, ())
/// Wraps up the necessary context to intercept global state.
type TestContexts =
private
{
/// Accesses to this must be locked on StdOutWriters.
StdOuts : Dictionary<OutputStreamId, MemoryStream>
/// Accesses to this must be locked on StdErrWriters.
StdErrs : Dictionary<OutputStreamId, MemoryStream>
StdOutWriters : Dictionary<OutputStreamId, TextWriter>
StdErrWriters : Dictionary<OutputStreamId, TextWriter>
StdOutWriter : TextWriter
StdErrWriter : TextWriter
AsyncLocal : AsyncLocal<OutputStreamId>
}
/// Call this exactly once.
static member Empty () =
let stdouts = Dictionary ()
let stderrs = Dictionary ()
let stdoutWriters = Dictionary ()
let stderrWriters = Dictionary ()
let local = AsyncLocal ()
let stdoutWriter = new ThreadAwareWriter (local, stdoutWriters, stdouts)
let stderrWriter = new ThreadAwareWriter (local, stderrWriters, stderrs)
{
StdOuts = stdouts
StdErrs = stderrs
StdOutWriter = stdoutWriter
StdErrWriter = stderrWriter
StdOutWriters = stdoutWriters
StdErrWriters = stderrWriters
AsyncLocal = local
}
/// An output stream which will identify the ExecutionContext it's being written to from,
/// and will separate that output into its own stream internally.
member this.Stdout : TextWriter = this.StdOutWriter
/// An output stream which will identify the ExecutionContext it's being written to from,
/// and will separate that output into its own stream internally.
member this.Stderr : TextWriter = this.StdErrWriter
member internal this.DumpStdout (id : OutputStreamId) : string =
lock
this.StdOutWriters
(fun () ->
this.StdOutWriters.[id].Flush ()
this.StdOuts.[id].ToArray ()
)
|> Encoding.Default.GetString
member internal this.DumpStderr (id : OutputStreamId) : string =
lock
this.StdErrWriters
(fun () ->
this.StdErrWriters.[id].Flush ()
this.StdErrs.[id].ToArray ()
)
|> Encoding.Default.GetString
member internal this.NewOutputs () =
let id = Guid.NewGuid () |> OutputStreamId
let msOut = new MemoryStream ()
let wrOut = new StreamWriter (msOut)
let msErr = new MemoryStream ()
let wrErr = new StreamWriter (msErr)
lock
this.StdOutWriters
(fun () ->
this.StdOutWriters.Add (id, wrOut)
this.StdOuts.Add (id, msOut)
)
lock
this.StdErrWriters
(fun () ->
this.StdErrWriters.Add (id, wrErr)
this.StdErrs.Add (id, msErr)
)
id
interface IDisposable with
member this.Dispose () =
// TODO: dispose the streams
()
/// A separate AssemblyLoadContext within which you can run the tests in the given DLL.
/// Supply places to find the .NET runtimes.
type LoadContext (dll : FileInfo, runtimes : DirectoryInfo list, contexts : TestContexts) =
inherit AssemblyLoadContext ()
/// Load the assembly with the given name into this assembly context.
/// This additionally monkey-patches System.Console: it performs SetOut and SetError on them
/// so that they redirect their outputs into the given `TestContexts`.
override this.Load (target : AssemblyName) : Assembly =
let path = Path.Combine (dll.Directory.FullName, $"%s{target.Name}.dll")
let assy =
if File.Exists path then
this.LoadFromAssemblyPath path
else
runtimes
|> List.tryPick (fun di ->
let path = Path.Combine (di.FullName, $"%s{target.Name}.dll")
if File.Exists path then
this.LoadFromAssemblyPath path |> Some
else
None
)
|> Option.defaultValue null
if target.Name = "System.Console" then
if isNull assy then
failwith "could not monkey-patch System.Console"
else
let consoleType = assy.GetType "System.Console"
let setOut = consoleType.GetMethod "SetOut"
setOut.Invoke ((null : obj), [| contexts.Stdout |]) |> unbox<unit>
let setErr = consoleType.GetMethod "SetError"
setErr.Invoke ((null : obj), [| contexts.Stderr |]) |> unbox<unit>
assy
else
assy

View File

@@ -0,0 +1,243 @@
namespace WoofWare.NUnitTestRunner
open System
open System.Reflection
/// Methods for constructing TRX reports.
[<RequireQualifiedAccess>]
module BuildTrxReport =
/// Build a TRX report from the given results.
let build
(assy : Assembly)
(creationTime : DateTimeOffset)
(startTime : DateTimeOffset)
(results : FixtureRunResults list)
: TrxReport
=
let finishTime = DateTimeOffset.Now
let finishTimeHumanReadable = finishTime.ToString @"yyyy-MM-dd HH:mm:ss"
let nowMachine = finishTime.ToString @"yyyy-MM-dd_HH_mm_ss"
let testListId = Guid.NewGuid ()
let testDefinitions, testEntries =
results
|> List.collect (fun results -> results.IndividualTestRunMetadata)
|> List.map (fun (data, _) ->
let defn =
{
Name = data.TestName
Storage = assy.Location.ToLowerInvariant ()
Id = data.TestId
Execution =
{
Id = data.ExecutionId
}
TestMethod =
{
CodeBase = assy.Location
AdapterTypeName = Uri "executor://woofware/"
ClassName = data.ClassName
Name = data.TestName
}
}
let entry : TrxTestEntry =
{
TestListId = testListId
ExecutionId = data.ExecutionId
TestId = data.TestId
}
defn, entry
)
|> List.unzip
let hostname = Environment.MachineName
let settings =
{
Name = "default"
Id = Guid.NewGuid ()
Deployment =
{
RunDeploymentRoot = $"_%s{hostname}_%s{nowMachine}"
}
}
let testList : TrxTestListEntry =
{
Id = testListId
Name = "All"
}
let counters =
(TrxCounters.Zero, results)
// TODO: this is woefully inefficient
||> List.fold (fun counters results ->
let counters =
(counters, results.Failed)
||> List.fold (fun counters (_, _) ->
// TODO: the counters can be more specific about the failure mode
counters.AddFailed ()
)
let counters =
(counters, results.OtherFailures)
||> List.fold (fun counters _ ->
// TODO: the counters can be more specific about the failure mode
counters.AddFailed ()
)
(counters, results.Success)
||> List.fold (fun counters (_, success, _) ->
match success with
| TestMemberSuccess.Ok -> counters.AddPassed ()
| TestMemberSuccess.Ignored _
| TestMemberSuccess.Explicit _ -> counters.AddNotExecuted ()
| TestMemberSuccess.Inconclusive _ -> counters.AddInconclusive ()
)
)
// TODO: I'm sure we can do better than this; there's a whole range of possible
// states!
let outcome =
if counters.Failed > 0u then
TrxOutcome.Failed
else
TrxOutcome.Completed
let resultSummary : TrxResultsSummary =
{
Outcome = outcome
Counters = counters
Output =
{
StdOut = None
StdErr = None
ErrorInfo = None
}
RunInfos =
[
// TODO: capture stdout
]
}
let times : TrxReportTimes =
{
Creation = creationTime
Queuing = startTime
Start = startTime
Finish = finishTime
}
let magicGuid = Guid.Parse "13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b"
let results =
results
|> List.collect (fun results -> results.IndividualTestRunMetadata)
|> List.map (fun (i, cause) ->
let exc =
match cause with
| Choice2Of3 _ -> None
| Choice1Of3 (TestMemberFailure.Malformed reasons) ->
{
StackTrace = None
Message = reasons |> String.concat "\n" |> Some
}
|> Some
| Choice1Of3 (TestMemberFailure.Failed fail)
| Choice1Of3 (TestMemberFailure.Failed fail)
| Choice1Of3 (TestMemberFailure.Failed fail) ->
((None, None), fail)
||> List.fold (fun (stackTrace, message) tf ->
match tf with
| TestFailure.TestFailed (UserMethodFailure.Threw (_, exc))
| TestFailure.SetUpFailed (UserMethodFailure.Threw (_, exc))
| TestFailure.TearDownFailed (UserMethodFailure.Threw (_, exc)) ->
let stackTrace =
match stackTrace with
| None -> (exc : Exception).ToString ()
| Some s -> s
(Some stackTrace, message)
| TestFailure.TestFailed (UserMethodFailure.ReturnedNonUnit (_, ret))
| TestFailure.SetUpFailed (UserMethodFailure.ReturnedNonUnit (_, ret))
| TestFailure.TearDownFailed (UserMethodFailure.ReturnedNonUnit (_, ret)) ->
let newMessage = $"returned non-unit value %O{ret}"
let message =
match message with
| None -> newMessage
| Some message -> $"%s{message}\n%s{newMessage}"
(stackTrace, Some message)
)
|> fun (stackTrace, message) ->
{
StackTrace = stackTrace
Message = message
}
|> Some
| Choice3Of3 (UserMethodFailure.Threw (_, exc)) ->
{
StackTrace = (exc : Exception).ToString () |> Some
Message = None
}
|> Some
| Choice3Of3 (UserMethodFailure.ReturnedNonUnit (_, ret)) ->
{
Message = $"returned non-unit value %O{ret}" |> Some
StackTrace = None
}
|> Some
let outcome =
match cause with
| Choice1Of3 _ -> TrxTestOutcome.Failed
| Choice2Of3 TestMemberSuccess.Ok -> TrxTestOutcome.Passed
| Choice2Of3 (TestMemberSuccess.Inconclusive _) -> TrxTestOutcome.Inconclusive
| Choice2Of3 (TestMemberSuccess.Ignored _)
| Choice2Of3 (TestMemberSuccess.Explicit _) -> TrxTestOutcome.NotExecuted
// TODO: we can totally do better here, more fine-grained classification
| Choice3Of3 _ -> TrxTestOutcome.Failed
{
ExecutionId = i.ExecutionId
TestId = i.TestId
TestName = i.TestName
ComputerName = i.ComputerName
Duration = i.End - i.Start
StartTime = i.Start
EndTime = i.End
TestType = magicGuid
Outcome = outcome
TestListId = testListId
RelativeResultsDirectory = i.ExecutionId.ToString () // for some reason
Output =
match i.StdOut, i.StdErr, exc with
| None, None, None -> None
| stdout, stderr, exc ->
Some
{
TrxOutput.StdOut = stdout
StdErr = stderr
ErrorInfo = exc
}
}
)
{
Id = Guid.NewGuid ()
Name = $"@%s{hostname} %s{finishTimeHumanReadable}"
Times = times
Settings = settings
Results = results
TestDefinitions = testDefinitions
TestEntries = testEntries
TestLists = [ testList ]
ResultsSummary = resultSummary
}

View File

@@ -1,5 +1,6 @@
namespace WoofWare.NUnitTestRunner namespace WoofWare.NUnitTestRunner
open System
open System.Reflection open System.Reflection
/// A modifier on whether a given test should be run. /// A modifier on whether a given test should be run.
@@ -30,6 +31,36 @@ type Combinatorial =
/// each", and so on. Spare slots are filled with `Unchecked.defaultof<_>`. /// each", and so on. Spare slots are filled with `Unchecked.defaultof<_>`.
| Sequential | Sequential
/// Describes the level of parallelism permitted in some context.
[<RequireQualifiedAccess>]
type ClassParallelScope =
/// "I may be run in parallel with other tests, although my children might not be able to run in parallel with each
/// other".
| Self
/// "The set of things I contain may be run in parallel with itself".
| Children
/// "Fixtures within me may be run in parallel with each other, but the tests within a given fixture might not
/// be runnable in parallel with each other".
| Fixtures
/// "All my descendents are happy to run in parallel with anything else, and also so am I".
| All
/// Describes the level of parallelism permitted within an assembly.
[<RequireQualifiedAccess>]
type AssemblyParallelScope =
/// "The set of things I contain may be run in parallel with itself".
| Children
/// "Fixtures within me may be run in parallel with each other, but the tests within a given fixture might not
/// necessarily be runnable in parallel with each other".
| Fixtures
/// Describes whether a test can be run concurrently with other tests.
type Parallelizable<'scope> =
/// This test is happy, under some conditions (specified by the scope), to be run alongside other tests.
| Yes of 'scope
/// This test must always be run on its own.
| No
/// A single method or member which holds some tests. (Often such a member will represent only one test, but e.g. /// A single method or member which holds some tests. (Often such a member will represent only one test, but e.g.
/// if it has [<TestCaseSource>] then it represents multiple tests.) /// if it has [<TestCaseSource>] then it represents multiple tests.)
type SingleTestMethod = type SingleTestMethod =
@@ -48,6 +79,8 @@ type SingleTestMethod =
/// If this test has data supplied by `[<Value>]` annotations, specifies how those annotations are combined /// If this test has data supplied by `[<Value>]` annotations, specifies how those annotations are combined
/// to produce the complete collection of args. /// to produce the complete collection of args.
Combinatorial : Combinatorial option Combinatorial : Combinatorial option
/// If this test has declared a parallelisability, that goes here.
Parallelize : Parallelizable<unit> option
} }
/// Human-readable name of this test method. /// Human-readable name of this test method.
@@ -55,6 +88,7 @@ type SingleTestMethod =
/// A test fixture (usually represented by the [<TestFixture>]` attribute), which may contain many tests, /// A test fixture (usually represented by the [<TestFixture>]` attribute), which may contain many tests,
/// each of which may run many times. /// each of which may run many times.
[<NoComparison>]
type TestFixture = type TestFixture =
{ {
/// The assembly which contains this TestFixture, loaded into a separate context. /// The assembly which contains this TestFixture, loaded into a separate context.
@@ -62,6 +96,8 @@ type TestFixture =
/// Fully-qualified name of this fixture (e.g. MyThing.Test.Foo for `[<TestFixture>] module Foo` in the /// Fully-qualified name of this fixture (e.g. MyThing.Test.Foo for `[<TestFixture>] module Foo` in the
/// `MyThing.Test` assembly). /// `MyThing.Test` assembly).
Name : string Name : string
/// The type which is this fixture, containing the tests as members.
Type : Type
/// A method which is run once when this test fixture starts, before any other setup logic and before /// A method which is run once when this test fixture starts, before any other setup logic and before
/// any tests run. If this method fails, no tests will run and no per-test setup/teardown logic will run, /// any tests run. If this method fails, no tests will run and no per-test setup/teardown logic will run,
/// but OneTimeTearDown will run. /// but OneTimeTearDown will run.
@@ -77,20 +113,28 @@ type TestFixture =
/// Methods which are run in some arbitrary order after each individual test, even if the test or its setup /// Methods which are run in some arbitrary order after each individual test, even if the test or its setup
/// failed. If the first TearDown we run fails, we don't define whether the other TearDowns run. /// failed. If the first TearDown we run fails, we don't define whether the other TearDowns run.
TearDown : MethodInfo list TearDown : MethodInfo list
/// You might have defined e.g. `[<TestFixture true>] type Foo (v : bool) = ...`. If so, this gives the
/// various possible parameters.
Parameters : obj list list
/// The individual test methods present within this fixture. /// The individual test methods present within this fixture.
Tests : SingleTestMethod list Tests : SingleTestMethod list
/// If this fixture has declared a parallelisability, that goes here.
Parallelize : Parallelizable<ClassParallelScope> option
} }
/// A test fixture about which we know nothing. No tests, no setup/teardown. /// A test fixture about which we know nothing. No tests, no setup/teardown.
static member Empty (containingAssembly : Assembly) (name : string) = static member Empty (ty : Type) (par : Parallelizable<ClassParallelScope> option) (args : obj list list) =
{ {
ContainingAssembly = containingAssembly ContainingAssembly = ty.Assembly
Name = name Type = ty
Name = ty.Name
OneTimeSetUp = None OneTimeSetUp = None
OneTimeTearDown = None OneTimeTearDown = None
SetUp = [] SetUp = []
TearDown = [] TearDown = []
Parameters = args
Tests = [] Tests = []
Parallelize = par
} }
/// User code in the unit under test has failed somehow. /// User code in the unit under test has failed somehow.

View File

@@ -0,0 +1,101 @@
namespace WoofWare.NUnitTestRunner
open System
open System.IO
open WoofWare.DotnetRuntimeLocator
/// Functions for locating .NET runtimes.
[<RequireQualifiedAccess>]
module DotnetRuntime =
let private selectRuntime (config : RuntimeOptions) (f : DotnetEnvironmentInfo) : DirectoryInfo list =
let rollForward =
match Environment.GetEnvironmentVariable "DOTNET_ROLL_FORWARD" with
| null ->
config.RollForward
|> Option.map RollForward.Parse
|> Option.defaultValue RollForward.Minor
| s -> RollForward.Parse s
if
Option.isSome config.IncludedFramework
|| Option.isSome config.IncludedFrameworks
then
// No need for a framework that's anywhere other than the given DLL.
[]
else
let desiredVersions =
match config.Framework with
| Some f -> [ Version f.Version, f.Name ]
| None ->
match config.Frameworks with
| Some f -> f |> List.map (fun f -> Version f.Version, f.Name)
| None ->
failwith
"Could not deduce a framework version due to lack of either Framework or Frameworks in runtimeconfig"
let compatiblyNamedRuntimes =
f.Frameworks
|> Seq.collect (fun availableFramework ->
desiredVersions
|> List.choose (fun (desiredVersion, desiredName) ->
if desiredName = availableFramework.Name then
Some
{|
Desired = desiredVersion
Name = desiredName
Installed = availableFramework
InstalledVersion = Version availableFramework.Version
|}
else
None
)
)
|> Seq.toList
match rollForward with
| RollForward.Minor ->
let available =
compatiblyNamedRuntimes
|> Seq.filter (fun data ->
data.InstalledVersion.Major = data.Desired.Major
&& data.InstalledVersion.Minor >= data.Desired.Minor
)
|> Seq.groupBy (fun data -> data.Name)
|> Seq.map (fun (name, data) ->
let data =
data
|> Seq.minBy (fun data -> data.InstalledVersion.Minor, data.InstalledVersion.Build)
name, data.Installed
)
|> Seq.toList
// TODO: maybe we can ask the SDK if we don't have any runtimes.
// But we keep on trucking: maybe we're self-contained, and we'll actually find all the runtime next to the
// DLL.
available
|> List.map (fun (_name, runtime) -> DirectoryInfo $"%s{runtime.Path}/%s{runtime.Version}")
| _ -> failwith "non-minor RollForward not supported yet; please shout if you want it"
/// Given an executable DLL, locate the .NET runtime that can best run it.
let locate (dll : FileInfo) : DirectoryInfo list =
let runtimeConfig =
let name =
if not (dll.Name.EndsWith (".dll", StringComparison.OrdinalIgnoreCase)) then
failwith $"Expected DLL %s{dll.FullName} to end in .dll"
dll.Name.Substring (0, dll.Name.Length - 4)
Path.Combine (dll.Directory.FullName, $"%s{name}.runtimeconfig.json")
|> File.ReadAllText
|> System.Text.Json.Nodes.JsonNode.Parse
|> RuntimeConfig.jsonParse
|> fun f -> f.RuntimeOptions
let availableRuntimes = DotnetEnvironmentInfo.Get ()
let runtime = selectRuntime runtimeConfig availableRuntimes
dll.Directory :: runtime

View File

@@ -2,7 +2,7 @@ namespace WoofWare.NUnitTestRunner
open System open System
open System.IO open System.IO
open PrattParser open WoofWare.PrattParser
// Documentation: // Documentation:
// https://learn.microsoft.com/en-us/dotnet/core/testing/selective-unit-tests?pivots=mstest // https://learn.microsoft.com/en-us/dotnet/core/testing/selective-unit-tests?pivots=mstest

View File

@@ -0,0 +1,422 @@
namespace WoofWare.NUnitTestRunner
open System
open System.Threading
open System.Threading.Tasks
type private ThunkEvaluator<'ret> =
abstract Eval<'a> : (unit -> 'a) -> AsyncReplyChannel<'a> -> 'ret
type private ThunkCrate =
abstract Apply<'ret> : ThunkEvaluator<'ret> -> 'ret
[<RequireQualifiedAccess>]
module private ThunkCrate =
let make<'a> (t : unit -> 'a) (rc : AsyncReplyChannel<'a>) : ThunkCrate =
{ new ThunkCrate with
member _.Apply e = e.Eval t rc
}
type private FakeUnit = FakeUnit
/// A handle to a running test fixture.
type TestFixtureRunningToken = private | TestFixtureRunningToken of TestFixture
/// A handle to a test fixture whose setup method has been called.
type TestFixtureSetupToken = private | TestFixtureSetupToken of TestFixture
[<RequireQualifiedAccess>]
module private TestFixtureSetupToken =
let vouchNoSetupRequired (TestFixtureRunningToken tf) = TestFixtureSetupToken tf
/// A handle to a test fixture whose setup method has been called.
type TestFixtureTearDownToken = private | TestFixtureTearDownToken of TestFixture
[<RequireQualifiedAccess>]
module private TestFixtureTearDownToken =
let vouchNoTearDownRequired (TestFixtureSetupToken tf) = TestFixtureTearDownToken tf
[<RequireQualifiedAccess>]
type private MailboxMessage =
| Quit of AsyncReplyChannel<unit>
/// Check current state, see if we need to start more tests, etc.
| Reconcile
| RunTest of within : TestFixture * Parallelizable<unit> option * test : ThunkCrate
| BeginTestFixture of TestFixture * AsyncReplyChannel<TestFixtureRunningToken>
| EndTestFixture of TestFixtureTearDownToken * AsyncReplyChannel<unit>
type private RunningFixture =
{
Fixture : TestFixture
RunningCanParallelize : bool
Running : Task list
Waiting : ((unit -> Task) * Parallelizable<unit> option) list
}
static member Make (f : TestFixture) =
{
Fixture = f
Running = []
RunningCanParallelize = true
Waiting = []
}
type private RunningState =
{
MaxParallelism : int
// TODO: make these efficiently look-up-able
CurrentlyRunning : RunningFixture list
Waiting : (TestFixture * AsyncReplyChannel<TestFixtureRunningToken>) list
}
member this.NewTest (tf : TestFixture) (par : Parallelizable<unit> option) (test : unit -> Task) =
{
MaxParallelism = this.MaxParallelism
Waiting = this.Waiting
CurrentlyRunning =
let found = ref 0
this.CurrentlyRunning
|> List.map (fun f ->
if Object.ReferenceEquals (f.Fixture, tf) then
Interlocked.Increment found |> ignore<int>
{ f with
Waiting = (test, par) :: f.Waiting
}
else
f
)
|> fun l ->
match found.Value with
| 1 -> l
| 0 -> failwith $"Unexpectedly did not find the running test fixture '%s{tf.Name}' to add a test to"
| _ -> failwith $"Unexpectedly found the running test fixture '%s{tf.Name}' multiple times in list"
}
member this.CompleteFixture (tf : TestFixture) : RunningState =
let rec go (acc : RunningFixture list) (running : RunningFixture list) =
match running with
| [] -> failwith "Caller has somehow called EndTestFixture while we're not running that test fixture"
| runningFixture :: tail ->
if Object.ReferenceEquals (runningFixture.Fixture, tf) then
match runningFixture.Running, runningFixture.Waiting with
| [], [] -> acc @ tail
| r, [] ->
failwith $"Caller has called EndTestFixture while its tests are still running (%i{r.Length})"
| [], r ->
failwith $"Caller has called EndTestFixture while it has tests waiting to run (%i{r.Length})"
| r, s ->
failwith
$"Caller has called EndTestFixture while it has tests waiting to run (%i{s.Length}) and test running (%i{r.Length})"
else
go (runningFixture :: acc) tail
let currentlyRunning = go [] this.CurrentlyRunning
{
CurrentlyRunning = currentlyRunning
Waiting = this.Waiting
MaxParallelism = this.MaxParallelism
}
type private MailboxState =
| Idle
| Running of RunningState
/// Run some things in parallel.
/// TODO: actually implement the parallelism! Right now this just runs everything serially.
/// TODO: consume the cancellation token
type ParallelQueue
(parallelism : int option, _scope : Parallelizable<AssemblyParallelScope> option, ?ct : CancellationToken)
=
let parallelism =
match parallelism with
| None -> max (Environment.ProcessorCount / 2) 2
| Some p -> p
let rec processTask (state : MailboxState) (m : MailboxProcessor<MailboxMessage>) =
async {
let! message = m.Receive ()
match message with
| MailboxMessage.Quit rc -> rc.Reply ()
| MailboxMessage.Reconcile ->
match state with
| Idle -> return! processTask state m
| Running r ->
match r.CurrentlyRunning with
| [] ->
match r.Waiting with
| [] -> return! processTask Idle m
| (head, rc) :: tail ->
rc.Reply (TestFixtureRunningToken head)
let newRunning =
{
Fixture = head
Running = []
RunningCanParallelize = true
Waiting = []
}
let state =
{
MaxParallelism = r.MaxParallelism
CurrentlyRunning = [ newRunning ]
Waiting = tail
}
// For now, we'll just run one fixture at a time. When we run multiple fixtures in parallel,
// we probably want to call Reconcile here again.
return! processTask (Running state) m
| [ currentlyRunning ] ->
let currentlyRunningTasks =
currentlyRunning.Running |> List.filter (fun t -> not t.IsCompleted)
let r =
{ r with
CurrentlyRunning =
[
{ currentlyRunning with
Running = currentlyRunningTasks
}
]
}
match currentlyRunningTasks with
| [] ->
match currentlyRunning.Waiting with
| [] ->
// Nothing to run yet
return! processTask (Running r) m
| (head, par) :: tail ->
let par =
match par with
| None -> true
| Some Parallelizable.No -> false
| Some (Parallelizable.Yes ()) -> true
let state =
{
Fixture = currentlyRunning.Fixture
RunningCanParallelize = par
Waiting = tail
Running = [ head () ]
}
m.Post MailboxMessage.Reconcile
return!
processTask
(Running
{ r with
CurrentlyRunning = [ state ]
})
m
| currentlyRunningTasks ->
if currentlyRunningTasks.Length >= parallelism then
return! processTask (Running r) m
else
match currentlyRunning.Waiting, currentlyRunning.RunningCanParallelize with
| [], _ ->
// No new candidates.
return! processTask (Running r) m
| _, false ->
// The running test(s) can't have others added.
return! processTask (Running r) m
| (head, par) :: tail, true ->
match par with
| Some Parallelizable.No -> return! processTask (Running r) m
| Some (Parallelizable.Yes ()) ->
let state =
{
RunningState.MaxParallelism = r.MaxParallelism
Waiting = r.Waiting
CurrentlyRunning =
[
{
Fixture = currentlyRunning.Fixture
RunningCanParallelize = true
Running = head () :: currentlyRunning.Running
Waiting = tail
}
]
}
m.Post MailboxMessage.Reconcile
return! processTask (Running state) m
| None ->
match currentlyRunning.Fixture.Parallelize with
| Some Parallelizable.No
| Some (Parallelizable.Yes ClassParallelScope.Self)
| Some (Parallelizable.Yes ClassParallelScope.Fixtures) ->
// Can't add this test to the parallel queue right now
return! processTask (Running r) m
| None
| Some (Parallelizable.Yes ClassParallelScope.All)
| Some (Parallelizable.Yes ClassParallelScope.Children) ->
let state =
{
Fixture = currentlyRunning.Fixture
RunningCanParallelize = true
Waiting = tail
Running = (head ()) :: currentlyRunningTasks
}
m.Post MailboxMessage.Reconcile
return!
processTask
(Running
{ r with
CurrentlyRunning = [ state ]
})
m
| _ -> failwith "Logic error: we currently only run one fixture at a time"
| MailboxMessage.BeginTestFixture (tf, rc) ->
match state with
| Running state ->
let state =
{
MaxParallelism = state.MaxParallelism
CurrentlyRunning = state.CurrentlyRunning
Waiting = (tf, rc) :: state.Waiting
}
|> Running
m.Post MailboxMessage.Reconcile
return! processTask state m
| Idle ->
let state =
{
MaxParallelism = parallelism
CurrentlyRunning = []
Waiting = [ (tf, rc) ]
}
|> Running
m.Post MailboxMessage.Reconcile
return! processTask state m
| MailboxMessage.EndTestFixture (TestFixtureTearDownToken tf, rc) ->
match state with
| Idle ->
return failwith "Caller has somehow called EndTestFixture while we're not running a test fixture"
| Running state ->
let state = state.CompleteFixture tf
rc.Reply ()
m.Post MailboxMessage.Reconcile
return! processTask (Running state) m
| MailboxMessage.RunTest (withinFixture, par, message) ->
let t () =
{ new ThunkEvaluator<_> with
member _.Eval<'b> (t : unit -> 'b) rc =
let tcs = TaskCompletionSource ()
use ec = ExecutionContext.Capture ()
fun () ->
ExecutionContext.Run (
ec,
(fun _ ->
let result = t ()
tcs.SetResult ()
m.Post MailboxMessage.Reconcile
rc.Reply result
),
()
)
|> Task.Factory.StartNew
|> ignore<Task>
tcs.Task
}
|> message.Apply
let state =
match state with
| Idle -> failwith "somehow asked the queue to run tests when there is no active fixture"
| Running state -> state.NewTest withinFixture par t |> Running
m.Post MailboxMessage.Reconcile
return! processTask state m
}
let mb = new MailboxProcessor<_> (processTask MailboxState.Idle)
do mb.Start ()
/// Request to run the given action, freely in parallel with other running tests.
/// The resulting Task will return when the action has completed.
member _.Run<'a>
(TestFixtureSetupToken parent)
(scope : Parallelizable<unit> option)
(action : unit -> 'a)
: 'a Task
=
(fun rc -> MailboxMessage.RunTest (parent, scope, ThunkCrate.make action rc))
|> mb.PostAndAsyncReply
|> Async.StartAsTask
/// Declare that we wish to start the given test fixture. The resulting Task will return
/// when you are allowed to start running tests from that fixture.
/// Once you've finished running tests from that fixture, call EndTestFixture.
member _.StartTestFixture (tf : TestFixture) : Task<TestFixtureRunningToken> =
fun rc -> MailboxMessage.BeginTestFixture (tf, rc)
|> mb.PostAndAsyncReply
|> Async.StartAsTask
/// Run the given one-time setup for the test fixture.
member _.RunTestSetup (TestFixtureRunningToken parent) (action : unit -> 'a) : ('a * TestFixtureSetupToken) Task =
task {
let par =
parent.Parallelize
|> Option.map (fun p ->
match p with
| Parallelizable.No -> Parallelizable.No
| Parallelizable.Yes _ -> Parallelizable.Yes ()
)
let! response =
(fun rc -> MailboxMessage.RunTest (parent, par, ThunkCrate.make action rc))
|> mb.PostAndAsyncReply
return response, TestFixtureSetupToken parent
}
/// Run the given one-time tear-down for the test fixture.
member _.RunTestTearDown
(TestFixtureSetupToken parent)
(action : unit -> 'a)
: ('a * TestFixtureTearDownToken) Task
=
task {
let par =
parent.Parallelize
|> Option.map (fun p ->
match p with
| Parallelizable.No -> Parallelizable.No
| Parallelizable.Yes _ -> Parallelizable.Yes ()
)
let! response =
(fun rc -> MailboxMessage.RunTest (parent, par, ThunkCrate.make action rc))
|> mb.PostAndAsyncReply
return response, TestFixtureTearDownToken parent
}
/// Declare that we have finished submitting requests to run in the given test fixture.
/// You don't need to worry about when the resulting Task returns, but we provide it just in case.
member _.EndTestFixture (tf : TestFixtureTearDownToken) : Task<unit> =
(fun rc -> MailboxMessage.EndTestFixture (tf, rc))
|> mb.PostAndAsyncReply
|> Async.StartAsTask
interface IDisposable with
member _.Dispose () =
// Still race conditions, of course: people could still be submitting after we finish the sync.
mb.PostAndReply MailboxMessage.Quit
(mb :> IDisposable).Dispose ()

View File

@@ -4,29 +4,31 @@ open System
open WoofWare.Myriad.Plugins open WoofWare.Myriad.Plugins
[<JsonParse>] [<JsonParse>]
type FrameworkDescription = type internal FrameworkDescription =
{ {
Name : string Name : string
Version : string Version : string
} }
[<JsonParse>] [<JsonParse>]
type RuntimeOptions = type internal RuntimeOptions =
{ {
Tfm : string Tfm : string
Framework : FrameworkDescription option Framework : FrameworkDescription option
Frameworks : FrameworkDescription list option Frameworks : FrameworkDescription list option
IncludedFramework : FrameworkDescription option
IncludedFrameworks : FrameworkDescription list option
RollForward : string option RollForward : string option
} }
[<JsonParse>] [<JsonParse>]
type RuntimeConfig = type internal RuntimeConfig =
{ {
RuntimeOptions : RuntimeOptions RuntimeOptions : RuntimeOptions
} }
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
type RollForward = type internal RollForward =
| Minor | Minor
| Major | Major
| LatestPatch | LatestPatch

View File

@@ -18,75 +18,95 @@ module SingleTestMethod =
(attrs : CustomAttributeData list) (attrs : CustomAttributeData list)
: SingleTestMethod option * CustomAttributeData list : SingleTestMethod option * CustomAttributeData list
= =
let remaining, isTest, sources, hasData, modifiers, categories, repeat, comb = let remaining, isTest, sources, hasData, modifiers, categories, repeat, comb, par =
(([], false, [], None, [], [], None, None), attrs) (([], false, [], None, [], [], None, None, None), attrs)
||> List.fold (fun (remaining, isTest, sources, hasData, mods, cats, repeat, comb) attr -> ||> List.fold (fun (remaining, isTest, sources, hasData, mods, cats, repeat, comb, par) attr ->
match attr.AttributeType.FullName with match attr.AttributeType.FullName with
| "NUnit.Framework.TestAttribute" -> | "NUnit.Framework.TestAttribute" ->
if attr.ConstructorArguments.Count > 0 then if attr.ConstructorArguments.Count > 0 then
failwith "Unexpectedly got arguments to the Test attribute" failwith "Unexpectedly got arguments to the Test attribute"
(remaining, true, sources, hasData, mods, cats, repeat, comb) (remaining, true, sources, hasData, mods, cats, repeat, comb, par)
| "NUnit.Framework.TestCaseAttribute" -> | "NUnit.Framework.TestCaseAttribute" ->
let args = attr.ConstructorArguments |> Seq.map _.Value |> Seq.toList let args = attr.ConstructorArguments |> Seq.map _.Value |> Seq.toList
let args =
match args with
| [ :? System.Collections.ICollection as x ] ->
x
|> Seq.cast<CustomAttributeTypedArgument>
|> Seq.map (fun v -> v.Value)
|> Seq.toList
| _ -> args
match hasData with match hasData with
| None -> (remaining, isTest, sources, Some [ List.ofSeq args ], mods, cats, repeat, comb) | None -> (remaining, isTest, sources, Some [ List.ofSeq args ], mods, cats, repeat, comb, par)
| Some existing -> | Some existing ->
(remaining, isTest, sources, Some ((List.ofSeq args) :: existing), mods, cats, repeat, comb) let args = (List.ofSeq args) :: existing |> Some
(remaining, isTest, sources, args, mods, cats, repeat, comb, par)
| "NUnit.Framework.TestCaseSourceAttribute" -> | "NUnit.Framework.TestCaseSourceAttribute" ->
let arg = attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox<string> let arg = attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox<string>
(remaining, isTest, arg :: sources, hasData, mods, cats, repeat, comb) (remaining, isTest, arg :: sources, hasData, mods, cats, repeat, comb, par)
| "NUnit.Framework.ExplicitAttribute" -> | "NUnit.Framework.ExplicitAttribute" ->
let reason = let reason =
attr.ConstructorArguments attr.ConstructorArguments
|> Seq.tryHead |> Seq.tryHead
|> Option.map (_.Value >> unbox<string>) |> Option.map (_.Value >> unbox<string>)
(remaining, isTest, sources, hasData, (Modifier.Explicit reason) :: mods, cats, repeat, comb) (remaining, isTest, sources, hasData, (Modifier.Explicit reason) :: mods, cats, repeat, comb, par)
| "NUnit.Framework.IgnoreAttribute" -> | "NUnit.Framework.IgnoreAttribute" ->
let reason = let reason =
attr.ConstructorArguments attr.ConstructorArguments
|> Seq.tryHead |> Seq.tryHead
|> Option.map (_.Value >> unbox<string>) |> Option.map (_.Value >> unbox<string>)
(remaining, isTest, sources, hasData, (Modifier.Ignored reason) :: mods, cats, repeat, comb) (remaining, isTest, sources, hasData, (Modifier.Ignored reason) :: mods, cats, repeat, comb, par)
| "NUnit.Framework.CategoryAttribute" -> | "NUnit.Framework.CategoryAttribute" ->
let category = let category =
attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox<string> attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox<string>
(remaining, isTest, sources, hasData, mods, category :: cats, repeat, comb) (remaining, isTest, sources, hasData, mods, category :: cats, repeat, comb, par)
| "NUnit.Framework.RepeatAttribute" -> | "NUnit.Framework.RepeatAttribute" ->
match repeat with match repeat with
| Some _ -> failwith $"Got RepeatAttribute multiple times on %s{method.Name}" | Some _ -> failwith $"Got RepeatAttribute multiple times on %s{method.Name}"
| None -> | None ->
let repeat = attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox<int> let repeat = attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox<int>
(remaining, isTest, sources, hasData, mods, cats, Some repeat, comb) (remaining, isTest, sources, hasData, mods, cats, Some repeat, comb, par)
| "NUnit.Framework.CombinatorialAttribute" -> | "NUnit.Framework.CombinatorialAttribute" ->
match comb with match comb with
| Some _ -> | Some _ ->
failwith $"Got CombinatorialAttribute or SequentialAttribute multiple times on %s{method.Name}" failwith $"Got CombinatorialAttribute or SequentialAttribute multiple times on %s{method.Name}"
| None -> | None ->
(remaining, isTest, sources, hasData, mods, cats, repeat, Some Combinatorial.Combinatorial) (remaining, isTest, sources, hasData, mods, cats, repeat, Some Combinatorial.Combinatorial, par)
| "NUnit.Framework.SequentialAttribute" -> | "NUnit.Framework.SequentialAttribute" ->
match comb with match comb with
| Some _ -> | Some _ ->
failwith $"Got CombinatorialAttribute or SequentialAttribute multiple times on %s{method.Name}" failwith $"Got CombinatorialAttribute or SequentialAttribute multiple times on %s{method.Name}"
| None -> (remaining, isTest, sources, hasData, mods, cats, repeat, Some Combinatorial.Sequential) | None ->
(remaining, isTest, sources, hasData, mods, cats, repeat, Some Combinatorial.Sequential, par)
| "NUnit.Framework.NonParallelizableAttribute" ->
match par with
| Some _ -> failwith $"Got a parallelization attribute multiple times on %s{method.Name}"
| None -> (remaining, isTest, sources, hasData, mods, cats, repeat, comb, Some Parallelizable.No)
| "NUnit.Framework.ParallelizableAttribute" ->
match par with
| Some _ -> failwith $"Got multiple parallelization attributes on %s{method.Name}"
| None ->
(remaining, isTest, sources, hasData, mods, cats, repeat, comb, Some (Parallelizable.Yes ()))
| s when s.StartsWith ("NUnit.Framework", StringComparison.Ordinal) -> | s when s.StartsWith ("NUnit.Framework", StringComparison.Ordinal) ->
failwith $"Unrecognised attribute on function %s{method.Name}: %s{attr.AttributeType.FullName}" failwith $"Unrecognised attribute on function %s{method.Name}: %s{attr.AttributeType.FullName}"
| _ -> (attr :: remaining, isTest, sources, hasData, mods, cats, repeat, comb) | _ -> (attr :: remaining, isTest, sources, hasData, mods, cats, repeat, comb, par)
) )
let test = let test =
match isTest, sources, hasData, modifiers, categories, repeat, comb with match isTest, sources, hasData, modifiers, categories, repeat, comb, par with
| _, _ :: _, Some _, _, _, _, _ -> | _, _ :: _, Some _, _, _, _, _, _ ->
failwith failwith
$"Test '%s{method.Name}' unexpectedly has both TestData and TestCaseSource; not currently supported" $"Test '%s{method.Name}' unexpectedly has both TestData and TestCaseSource; not currently supported"
| false, [], None, [], _, _, _ -> None | false, [], None, [], _, _, _, _ -> None
| _, _ :: _, None, mods, categories, repeat, comb -> | _, _ :: _, None, mods, categories, repeat, comb, par ->
{ {
Kind = TestKind.Source sources Kind = TestKind.Source sources
Method = method Method = method
@@ -94,9 +114,10 @@ module SingleTestMethod =
Categories = categories @ parentCategories Categories = categories @ parentCategories
Repeat = repeat Repeat = repeat
Combinatorial = comb Combinatorial = comb
Parallelize = par
} }
|> Some |> Some
| _, [], Some data, mods, categories, repeat, comb -> | _, [], Some data, mods, categories, repeat, comb, par ->
{ {
Kind = TestKind.Data data Kind = TestKind.Data data
Method = method Method = method
@@ -104,9 +125,10 @@ module SingleTestMethod =
Categories = categories @ parentCategories Categories = categories @ parentCategories
Repeat = repeat Repeat = repeat
Combinatorial = comb Combinatorial = comb
Parallelize = par
} }
|> Some |> Some
| true, [], None, mods, categories, repeat, comb -> | true, [], None, mods, categories, repeat, comb, par ->
{ {
Kind = TestKind.Single Kind = TestKind.Single
Method = method Method = method
@@ -114,9 +136,10 @@ module SingleTestMethod =
Categories = categories @ parentCategories Categories = categories @ parentCategories
Repeat = repeat Repeat = repeat
Combinatorial = comb Combinatorial = comb
Parallelize = par
} }
|> Some |> Some
| false, [], None, _ :: _, _, _, _ -> | false, [], None, _ :: _, _, _, _, _ ->
failwith failwith
$"Unexpectedly got test modifiers but no test settings on '%s{method.Name}', which you probably didn't intend." $"Unexpectedly got test modifiers but no test settings on '%s{method.Name}', which you probably didn't intend."

View File

@@ -1,3 +1,66 @@
WoofWare.NUnitTestRunner.Args inherit obj, implements WoofWare.NUnitTestRunner.Args System.IEquatable, System.Collections.IStructuralEquatable
WoofWare.NUnitTestRunner.Args..ctor [constructor]: (System.IO.FileInfo, System.IO.FileInfo option, (string * WoofWare.NUnitTestRunner.Filter) option, WoofWare.NUnitTestRunner.LogLevel, int option, System.TimeSpan option)
WoofWare.NUnitTestRunner.Args.Dll [property]: [read-only] System.IO.FileInfo
WoofWare.NUnitTestRunner.Args.Filter [property]: [read-only] (string * WoofWare.NUnitTestRunner.Filter) option
WoofWare.NUnitTestRunner.Args.get_Dll [method]: unit -> System.IO.FileInfo
WoofWare.NUnitTestRunner.Args.get_Filter [method]: unit -> (string * WoofWare.NUnitTestRunner.Filter) option
WoofWare.NUnitTestRunner.Args.get_LevelOfParallelism [method]: unit -> int option
WoofWare.NUnitTestRunner.Args.get_Logging [method]: unit -> WoofWare.NUnitTestRunner.LogLevel
WoofWare.NUnitTestRunner.Args.get_Timeout [method]: unit -> System.TimeSpan option
WoofWare.NUnitTestRunner.Args.get_Trx [method]: unit -> System.IO.FileInfo option
WoofWare.NUnitTestRunner.Args.LevelOfParallelism [property]: [read-only] int option
WoofWare.NUnitTestRunner.Args.Logging [property]: [read-only] WoofWare.NUnitTestRunner.LogLevel
WoofWare.NUnitTestRunner.Args.Parse [static method]: string list -> WoofWare.NUnitTestRunner.Args
WoofWare.NUnitTestRunner.Args.Timeout [property]: [read-only] System.TimeSpan option
WoofWare.NUnitTestRunner.Args.Trx [property]: [read-only] System.IO.FileInfo option
WoofWare.NUnitTestRunner.AssemblyLevelAttributes inherit obj, implements WoofWare.NUnitTestRunner.AssemblyLevelAttributes System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.AssemblyLevelAttributes System.IComparable, System.IComparable, System.Collections.IStructuralComparable
WoofWare.NUnitTestRunner.AssemblyLevelAttributes..ctor [constructor]: (int option, WoofWare.NUnitTestRunner.AssemblyParallelScope WoofWare.NUnitTestRunner.Parallelizable option)
WoofWare.NUnitTestRunner.AssemblyLevelAttributes.get_Parallelism [method]: unit -> int option
WoofWare.NUnitTestRunner.AssemblyLevelAttributes.get_Parallelizable [method]: unit -> WoofWare.NUnitTestRunner.AssemblyParallelScope WoofWare.NUnitTestRunner.Parallelizable option
WoofWare.NUnitTestRunner.AssemblyLevelAttributes.Parallelism [property]: [read-only] int option
WoofWare.NUnitTestRunner.AssemblyLevelAttributes.Parallelizable [property]: [read-only] WoofWare.NUnitTestRunner.AssemblyParallelScope WoofWare.NUnitTestRunner.Parallelizable option
WoofWare.NUnitTestRunner.AssemblyLevelAttributesModule inherit obj
WoofWare.NUnitTestRunner.AssemblyLevelAttributesModule.get [static method]: System.Reflection.Assembly -> WoofWare.NUnitTestRunner.AssemblyLevelAttributes
WoofWare.NUnitTestRunner.AssemblyParallelScope inherit obj, implements WoofWare.NUnitTestRunner.AssemblyParallelScope System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.AssemblyParallelScope System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 2 cases
WoofWare.NUnitTestRunner.AssemblyParallelScope+Tags inherit obj
WoofWare.NUnitTestRunner.AssemblyParallelScope+Tags.Children [static field]: int = 0
WoofWare.NUnitTestRunner.AssemblyParallelScope+Tags.Fixtures [static field]: int = 1
WoofWare.NUnitTestRunner.AssemblyParallelScope.Children [static property]: [read-only] WoofWare.NUnitTestRunner.AssemblyParallelScope
WoofWare.NUnitTestRunner.AssemblyParallelScope.Fixtures [static property]: [read-only] WoofWare.NUnitTestRunner.AssemblyParallelScope
WoofWare.NUnitTestRunner.AssemblyParallelScope.get_Children [static method]: unit -> WoofWare.NUnitTestRunner.AssemblyParallelScope
WoofWare.NUnitTestRunner.AssemblyParallelScope.get_Fixtures [static method]: unit -> WoofWare.NUnitTestRunner.AssemblyParallelScope
WoofWare.NUnitTestRunner.AssemblyParallelScope.get_IsChildren [method]: unit -> bool
WoofWare.NUnitTestRunner.AssemblyParallelScope.get_IsFixtures [method]: unit -> bool
WoofWare.NUnitTestRunner.AssemblyParallelScope.get_Tag [method]: unit -> int
WoofWare.NUnitTestRunner.AssemblyParallelScope.IsChildren [property]: [read-only] bool
WoofWare.NUnitTestRunner.AssemblyParallelScope.IsFixtures [property]: [read-only] bool
WoofWare.NUnitTestRunner.AssemblyParallelScope.Tag [property]: [read-only] int
WoofWare.NUnitTestRunner.BuildTrxReport inherit obj
WoofWare.NUnitTestRunner.BuildTrxReport.build [static method]: System.Reflection.Assembly -> System.DateTimeOffset -> System.DateTimeOffset -> WoofWare.NUnitTestRunner.FixtureRunResults list -> WoofWare.NUnitTestRunner.TrxReport
WoofWare.NUnitTestRunner.ClassParallelScope inherit obj, implements WoofWare.NUnitTestRunner.ClassParallelScope System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.ClassParallelScope System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 4 cases
WoofWare.NUnitTestRunner.ClassParallelScope+Tags inherit obj
WoofWare.NUnitTestRunner.ClassParallelScope+Tags.All [static field]: int = 3
WoofWare.NUnitTestRunner.ClassParallelScope+Tags.Children [static field]: int = 1
WoofWare.NUnitTestRunner.ClassParallelScope+Tags.Fixtures [static field]: int = 2
WoofWare.NUnitTestRunner.ClassParallelScope+Tags.Self [static field]: int = 0
WoofWare.NUnitTestRunner.ClassParallelScope.All [static property]: [read-only] WoofWare.NUnitTestRunner.ClassParallelScope
WoofWare.NUnitTestRunner.ClassParallelScope.Children [static property]: [read-only] WoofWare.NUnitTestRunner.ClassParallelScope
WoofWare.NUnitTestRunner.ClassParallelScope.Fixtures [static property]: [read-only] WoofWare.NUnitTestRunner.ClassParallelScope
WoofWare.NUnitTestRunner.ClassParallelScope.get_All [static method]: unit -> WoofWare.NUnitTestRunner.ClassParallelScope
WoofWare.NUnitTestRunner.ClassParallelScope.get_Children [static method]: unit -> WoofWare.NUnitTestRunner.ClassParallelScope
WoofWare.NUnitTestRunner.ClassParallelScope.get_Fixtures [static method]: unit -> WoofWare.NUnitTestRunner.ClassParallelScope
WoofWare.NUnitTestRunner.ClassParallelScope.get_IsAll [method]: unit -> bool
WoofWare.NUnitTestRunner.ClassParallelScope.get_IsChildren [method]: unit -> bool
WoofWare.NUnitTestRunner.ClassParallelScope.get_IsFixtures [method]: unit -> bool
WoofWare.NUnitTestRunner.ClassParallelScope.get_IsSelf [method]: unit -> bool
WoofWare.NUnitTestRunner.ClassParallelScope.get_Self [static method]: unit -> WoofWare.NUnitTestRunner.ClassParallelScope
WoofWare.NUnitTestRunner.ClassParallelScope.get_Tag [method]: unit -> int
WoofWare.NUnitTestRunner.ClassParallelScope.IsAll [property]: [read-only] bool
WoofWare.NUnitTestRunner.ClassParallelScope.IsChildren [property]: [read-only] bool
WoofWare.NUnitTestRunner.ClassParallelScope.IsFixtures [property]: [read-only] bool
WoofWare.NUnitTestRunner.ClassParallelScope.IsSelf [property]: [read-only] bool
WoofWare.NUnitTestRunner.ClassParallelScope.Self [static property]: [read-only] WoofWare.NUnitTestRunner.ClassParallelScope
WoofWare.NUnitTestRunner.ClassParallelScope.Tag [property]: [read-only] int
WoofWare.NUnitTestRunner.Combinatorial inherit obj, implements WoofWare.NUnitTestRunner.Combinatorial System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.Combinatorial System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 2 cases WoofWare.NUnitTestRunner.Combinatorial inherit obj, implements WoofWare.NUnitTestRunner.Combinatorial System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.Combinatorial System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 2 cases
WoofWare.NUnitTestRunner.Combinatorial+Tags inherit obj WoofWare.NUnitTestRunner.Combinatorial+Tags inherit obj
WoofWare.NUnitTestRunner.Combinatorial+Tags.Combinatorial [static field]: int = 0 WoofWare.NUnitTestRunner.Combinatorial+Tags.Combinatorial [static field]: int = 0
@@ -12,6 +75,8 @@ WoofWare.NUnitTestRunner.Combinatorial.IsCombinatorial [property]: [read-only] b
WoofWare.NUnitTestRunner.Combinatorial.IsSequential [property]: [read-only] bool WoofWare.NUnitTestRunner.Combinatorial.IsSequential [property]: [read-only] bool
WoofWare.NUnitTestRunner.Combinatorial.Sequential [static property]: [read-only] WoofWare.NUnitTestRunner.Combinatorial WoofWare.NUnitTestRunner.Combinatorial.Sequential [static property]: [read-only] WoofWare.NUnitTestRunner.Combinatorial
WoofWare.NUnitTestRunner.Combinatorial.Tag [property]: [read-only] int WoofWare.NUnitTestRunner.Combinatorial.Tag [property]: [read-only] int
WoofWare.NUnitTestRunner.DotnetRuntime inherit obj
WoofWare.NUnitTestRunner.DotnetRuntime.locate [static method]: System.IO.FileInfo -> System.IO.DirectoryInfo list
WoofWare.NUnitTestRunner.Filter inherit obj, implements WoofWare.NUnitTestRunner.Filter System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.Filter System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 6 cases WoofWare.NUnitTestRunner.Filter inherit obj, implements WoofWare.NUnitTestRunner.Filter System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.Filter System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 6 cases
WoofWare.NUnitTestRunner.Filter+And inherit WoofWare.NUnitTestRunner.Filter WoofWare.NUnitTestRunner.Filter+And inherit WoofWare.NUnitTestRunner.Filter
WoofWare.NUnitTestRunner.Filter+And.get_Item1 [method]: unit -> WoofWare.NUnitTestRunner.Filter WoofWare.NUnitTestRunner.Filter+And.get_Item1 [method]: unit -> WoofWare.NUnitTestRunner.Filter
@@ -103,6 +168,22 @@ WoofWare.NUnitTestRunner.ITestProgress.OnTestFixtureStart [method]: string -> in
WoofWare.NUnitTestRunner.ITestProgress.OnTestMemberFinished [method]: string -> unit WoofWare.NUnitTestRunner.ITestProgress.OnTestMemberFinished [method]: string -> unit
WoofWare.NUnitTestRunner.ITestProgress.OnTestMemberSkipped [method]: string -> unit WoofWare.NUnitTestRunner.ITestProgress.OnTestMemberSkipped [method]: string -> unit
WoofWare.NUnitTestRunner.ITestProgress.OnTestMemberStart [method]: string -> unit WoofWare.NUnitTestRunner.ITestProgress.OnTestMemberStart [method]: string -> unit
WoofWare.NUnitTestRunner.LoadContext inherit System.Runtime.Loader.AssemblyLoadContext
WoofWare.NUnitTestRunner.LoadContext..ctor [constructor]: (System.IO.FileInfo, System.IO.DirectoryInfo list, WoofWare.NUnitTestRunner.TestContexts)
WoofWare.NUnitTestRunner.LogLevel inherit obj, implements WoofWare.NUnitTestRunner.LogLevel System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.LogLevel System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 2 cases
WoofWare.NUnitTestRunner.LogLevel+Tags inherit obj
WoofWare.NUnitTestRunner.LogLevel+Tags.Nothing [static field]: int = 0
WoofWare.NUnitTestRunner.LogLevel+Tags.Verbose [static field]: int = 1
WoofWare.NUnitTestRunner.LogLevel.get_IsNothing [method]: unit -> bool
WoofWare.NUnitTestRunner.LogLevel.get_IsVerbose [method]: unit -> bool
WoofWare.NUnitTestRunner.LogLevel.get_Nothing [static method]: unit -> WoofWare.NUnitTestRunner.LogLevel
WoofWare.NUnitTestRunner.LogLevel.get_Tag [method]: unit -> int
WoofWare.NUnitTestRunner.LogLevel.get_Verbose [static method]: unit -> WoofWare.NUnitTestRunner.LogLevel
WoofWare.NUnitTestRunner.LogLevel.IsNothing [property]: [read-only] bool
WoofWare.NUnitTestRunner.LogLevel.IsVerbose [property]: [read-only] bool
WoofWare.NUnitTestRunner.LogLevel.Nothing [static property]: [read-only] WoofWare.NUnitTestRunner.LogLevel
WoofWare.NUnitTestRunner.LogLevel.Tag [property]: [read-only] int
WoofWare.NUnitTestRunner.LogLevel.Verbose [static property]: [read-only] WoofWare.NUnitTestRunner.LogLevel
WoofWare.NUnitTestRunner.Match inherit obj, implements WoofWare.NUnitTestRunner.Match System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.Match System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 2 cases WoofWare.NUnitTestRunner.Match inherit obj, implements WoofWare.NUnitTestRunner.Match System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.NUnitTestRunner.Match System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 2 cases
WoofWare.NUnitTestRunner.Match+Contains inherit WoofWare.NUnitTestRunner.Match WoofWare.NUnitTestRunner.Match+Contains inherit WoofWare.NUnitTestRunner.Match
WoofWare.NUnitTestRunner.Match+Contains.get_Item [method]: unit -> string WoofWare.NUnitTestRunner.Match+Contains.get_Item [method]: unit -> string
@@ -139,8 +220,31 @@ WoofWare.NUnitTestRunner.Modifier.IsIgnored [property]: [read-only] bool
WoofWare.NUnitTestRunner.Modifier.NewExplicit [static method]: string option -> WoofWare.NUnitTestRunner.Modifier WoofWare.NUnitTestRunner.Modifier.NewExplicit [static method]: string option -> WoofWare.NUnitTestRunner.Modifier
WoofWare.NUnitTestRunner.Modifier.NewIgnored [static method]: string option -> WoofWare.NUnitTestRunner.Modifier WoofWare.NUnitTestRunner.Modifier.NewIgnored [static method]: string option -> WoofWare.NUnitTestRunner.Modifier
WoofWare.NUnitTestRunner.Modifier.Tag [property]: [read-only] int WoofWare.NUnitTestRunner.Modifier.Tag [property]: [read-only] int
WoofWare.NUnitTestRunner.Parallelizable`1 inherit obj, implements 'scope WoofWare.NUnitTestRunner.Parallelizable System.IEquatable, System.Collections.IStructuralEquatable, 'scope WoofWare.NUnitTestRunner.Parallelizable System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 2 cases
WoofWare.NUnitTestRunner.Parallelizable`1+Tags inherit obj
WoofWare.NUnitTestRunner.Parallelizable`1+Tags.No [static field]: int = 1
WoofWare.NUnitTestRunner.Parallelizable`1+Tags.Yes [static field]: int = 0
WoofWare.NUnitTestRunner.Parallelizable`1+Yes inherit 'scope WoofWare.NUnitTestRunner.Parallelizable
WoofWare.NUnitTestRunner.Parallelizable`1+Yes.get_Item [method]: unit -> 'scope
WoofWare.NUnitTestRunner.Parallelizable`1+Yes.Item [property]: [read-only] 'scope
WoofWare.NUnitTestRunner.Parallelizable`1.get_IsNo [method]: unit -> bool
WoofWare.NUnitTestRunner.Parallelizable`1.get_IsYes [method]: unit -> bool
WoofWare.NUnitTestRunner.Parallelizable`1.get_No [static method]: unit -> 'scope WoofWare.NUnitTestRunner.Parallelizable
WoofWare.NUnitTestRunner.Parallelizable`1.get_Tag [method]: unit -> int
WoofWare.NUnitTestRunner.Parallelizable`1.IsNo [property]: [read-only] bool
WoofWare.NUnitTestRunner.Parallelizable`1.IsYes [property]: [read-only] bool
WoofWare.NUnitTestRunner.Parallelizable`1.NewYes [static method]: 'scope -> 'scope WoofWare.NUnitTestRunner.Parallelizable
WoofWare.NUnitTestRunner.Parallelizable`1.No [static property]: [read-only] 'scope WoofWare.NUnitTestRunner.Parallelizable
WoofWare.NUnitTestRunner.Parallelizable`1.Tag [property]: [read-only] int
WoofWare.NUnitTestRunner.ParallelQueue inherit obj, implements IDisposable
WoofWare.NUnitTestRunner.ParallelQueue..ctor [constructor]: (int option, WoofWare.NUnitTestRunner.AssemblyParallelScope WoofWare.NUnitTestRunner.Parallelizable option, System.Threading.CancellationToken option)
WoofWare.NUnitTestRunner.ParallelQueue.EndTestFixture [method]: WoofWare.NUnitTestRunner.TestFixtureTearDownToken -> unit System.Threading.Tasks.Task
WoofWare.NUnitTestRunner.ParallelQueue.Run [method]: WoofWare.NUnitTestRunner.TestFixtureSetupToken -> unit WoofWare.NUnitTestRunner.Parallelizable option -> (unit -> 'a) -> 'a System.Threading.Tasks.Task
WoofWare.NUnitTestRunner.ParallelQueue.RunTestSetup [method]: WoofWare.NUnitTestRunner.TestFixtureRunningToken -> (unit -> 'a) -> ('a * WoofWare.NUnitTestRunner.TestFixtureSetupToken) System.Threading.Tasks.Task
WoofWare.NUnitTestRunner.ParallelQueue.RunTestTearDown [method]: WoofWare.NUnitTestRunner.TestFixtureSetupToken -> (unit -> 'a) -> ('a * WoofWare.NUnitTestRunner.TestFixtureTearDownToken) System.Threading.Tasks.Task
WoofWare.NUnitTestRunner.ParallelQueue.StartTestFixture [method]: WoofWare.NUnitTestRunner.TestFixture -> WoofWare.NUnitTestRunner.TestFixtureRunningToken System.Threading.Tasks.Task
WoofWare.NUnitTestRunner.SingleTestMethod inherit obj, implements WoofWare.NUnitTestRunner.SingleTestMethod System.IEquatable, System.Collections.IStructuralEquatable WoofWare.NUnitTestRunner.SingleTestMethod inherit obj, implements WoofWare.NUnitTestRunner.SingleTestMethod System.IEquatable, System.Collections.IStructuralEquatable
WoofWare.NUnitTestRunner.SingleTestMethod..ctor [constructor]: (System.Reflection.MethodInfo, WoofWare.NUnitTestRunner.TestKind, WoofWare.NUnitTestRunner.Modifier list, string list, int option, WoofWare.NUnitTestRunner.Combinatorial option) WoofWare.NUnitTestRunner.SingleTestMethod..ctor [constructor]: (System.Reflection.MethodInfo, WoofWare.NUnitTestRunner.TestKind, WoofWare.NUnitTestRunner.Modifier list, string list, int option, WoofWare.NUnitTestRunner.Combinatorial option, unit WoofWare.NUnitTestRunner.Parallelizable option)
WoofWare.NUnitTestRunner.SingleTestMethod.Categories [property]: [read-only] string list WoofWare.NUnitTestRunner.SingleTestMethod.Categories [property]: [read-only] string list
WoofWare.NUnitTestRunner.SingleTestMethod.Combinatorial [property]: [read-only] WoofWare.NUnitTestRunner.Combinatorial option WoofWare.NUnitTestRunner.SingleTestMethod.Combinatorial [property]: [read-only] WoofWare.NUnitTestRunner.Combinatorial option
WoofWare.NUnitTestRunner.SingleTestMethod.get_Categories [method]: unit -> string list WoofWare.NUnitTestRunner.SingleTestMethod.get_Categories [method]: unit -> string list
@@ -149,14 +253,22 @@ WoofWare.NUnitTestRunner.SingleTestMethod.get_Kind [method]: unit -> WoofWare.NU
WoofWare.NUnitTestRunner.SingleTestMethod.get_Method [method]: unit -> System.Reflection.MethodInfo WoofWare.NUnitTestRunner.SingleTestMethod.get_Method [method]: unit -> System.Reflection.MethodInfo
WoofWare.NUnitTestRunner.SingleTestMethod.get_Modifiers [method]: unit -> WoofWare.NUnitTestRunner.Modifier list WoofWare.NUnitTestRunner.SingleTestMethod.get_Modifiers [method]: unit -> WoofWare.NUnitTestRunner.Modifier list
WoofWare.NUnitTestRunner.SingleTestMethod.get_Name [method]: unit -> string WoofWare.NUnitTestRunner.SingleTestMethod.get_Name [method]: unit -> string
WoofWare.NUnitTestRunner.SingleTestMethod.get_Parallelize [method]: unit -> unit WoofWare.NUnitTestRunner.Parallelizable option
WoofWare.NUnitTestRunner.SingleTestMethod.get_Repeat [method]: unit -> int option WoofWare.NUnitTestRunner.SingleTestMethod.get_Repeat [method]: unit -> int option
WoofWare.NUnitTestRunner.SingleTestMethod.Kind [property]: [read-only] WoofWare.NUnitTestRunner.TestKind WoofWare.NUnitTestRunner.SingleTestMethod.Kind [property]: [read-only] WoofWare.NUnitTestRunner.TestKind
WoofWare.NUnitTestRunner.SingleTestMethod.Method [property]: [read-only] System.Reflection.MethodInfo WoofWare.NUnitTestRunner.SingleTestMethod.Method [property]: [read-only] System.Reflection.MethodInfo
WoofWare.NUnitTestRunner.SingleTestMethod.Modifiers [property]: [read-only] WoofWare.NUnitTestRunner.Modifier list WoofWare.NUnitTestRunner.SingleTestMethod.Modifiers [property]: [read-only] WoofWare.NUnitTestRunner.Modifier list
WoofWare.NUnitTestRunner.SingleTestMethod.Name [property]: [read-only] string WoofWare.NUnitTestRunner.SingleTestMethod.Name [property]: [read-only] string
WoofWare.NUnitTestRunner.SingleTestMethod.Parallelize [property]: [read-only] unit WoofWare.NUnitTestRunner.Parallelizable option
WoofWare.NUnitTestRunner.SingleTestMethod.Repeat [property]: [read-only] int option WoofWare.NUnitTestRunner.SingleTestMethod.Repeat [property]: [read-only] int option
WoofWare.NUnitTestRunner.SingleTestMethodModule inherit obj WoofWare.NUnitTestRunner.SingleTestMethodModule inherit obj
WoofWare.NUnitTestRunner.SingleTestMethodModule.parse [static method]: string list -> System.Reflection.MethodInfo -> System.Reflection.CustomAttributeData list -> (WoofWare.NUnitTestRunner.SingleTestMethod option * System.Reflection.CustomAttributeData list) WoofWare.NUnitTestRunner.SingleTestMethodModule.parse [static method]: string list -> System.Reflection.MethodInfo -> System.Reflection.CustomAttributeData list -> (WoofWare.NUnitTestRunner.SingleTestMethod option * System.Reflection.CustomAttributeData list)
WoofWare.NUnitTestRunner.TestContexts inherit obj, implements WoofWare.NUnitTestRunner.TestContexts System.IEquatable, System.Collections.IStructuralEquatable, IDisposable
WoofWare.NUnitTestRunner.TestContexts.Empty [static method]: unit -> WoofWare.NUnitTestRunner.TestContexts
WoofWare.NUnitTestRunner.TestContexts.get_Stderr [method]: unit -> System.IO.TextWriter
WoofWare.NUnitTestRunner.TestContexts.get_Stdout [method]: unit -> System.IO.TextWriter
WoofWare.NUnitTestRunner.TestContexts.Stderr [property]: [read-only] System.IO.TextWriter
WoofWare.NUnitTestRunner.TestContexts.Stdout [property]: [read-only] System.IO.TextWriter
WoofWare.NUnitTestRunner.TestFailure inherit obj, implements WoofWare.NUnitTestRunner.TestFailure System.IEquatable, System.Collections.IStructuralEquatable - union type with 3 cases WoofWare.NUnitTestRunner.TestFailure inherit obj, implements WoofWare.NUnitTestRunner.TestFailure System.IEquatable, System.Collections.IStructuralEquatable - union type with 3 cases
WoofWare.NUnitTestRunner.TestFailure+SetUpFailed inherit WoofWare.NUnitTestRunner.TestFailure WoofWare.NUnitTestRunner.TestFailure+SetUpFailed inherit WoofWare.NUnitTestRunner.TestFailure
WoofWare.NUnitTestRunner.TestFailure+SetUpFailed.get_Item [method]: unit -> WoofWare.NUnitTestRunner.UserMethodFailure WoofWare.NUnitTestRunner.TestFailure+SetUpFailed.get_Item [method]: unit -> WoofWare.NUnitTestRunner.UserMethodFailure
@@ -185,25 +297,35 @@ WoofWare.NUnitTestRunner.TestFailure.NewTearDownFailed [static method]: WoofWare
WoofWare.NUnitTestRunner.TestFailure.NewTestFailed [static method]: WoofWare.NUnitTestRunner.UserMethodFailure -> WoofWare.NUnitTestRunner.TestFailure WoofWare.NUnitTestRunner.TestFailure.NewTestFailed [static method]: WoofWare.NUnitTestRunner.UserMethodFailure -> WoofWare.NUnitTestRunner.TestFailure
WoofWare.NUnitTestRunner.TestFailure.Tag [property]: [read-only] int WoofWare.NUnitTestRunner.TestFailure.Tag [property]: [read-only] int
WoofWare.NUnitTestRunner.TestFixture inherit obj, implements WoofWare.NUnitTestRunner.TestFixture System.IEquatable, System.Collections.IStructuralEquatable WoofWare.NUnitTestRunner.TestFixture inherit obj, implements WoofWare.NUnitTestRunner.TestFixture System.IEquatable, System.Collections.IStructuralEquatable
WoofWare.NUnitTestRunner.TestFixture..ctor [constructor]: (System.Reflection.Assembly, string, System.Reflection.MethodInfo option, System.Reflection.MethodInfo option, System.Reflection.MethodInfo list, System.Reflection.MethodInfo list, WoofWare.NUnitTestRunner.SingleTestMethod list) WoofWare.NUnitTestRunner.TestFixture..ctor [constructor]: (System.Reflection.Assembly, string, System.Type, System.Reflection.MethodInfo option, System.Reflection.MethodInfo option, System.Reflection.MethodInfo list, System.Reflection.MethodInfo list, obj list list, WoofWare.NUnitTestRunner.SingleTestMethod list, WoofWare.NUnitTestRunner.ClassParallelScope WoofWare.NUnitTestRunner.Parallelizable option)
WoofWare.NUnitTestRunner.TestFixture.ContainingAssembly [property]: [read-only] System.Reflection.Assembly WoofWare.NUnitTestRunner.TestFixture.ContainingAssembly [property]: [read-only] System.Reflection.Assembly
WoofWare.NUnitTestRunner.TestFixture.Empty [static method]: System.Reflection.Assembly -> string -> WoofWare.NUnitTestRunner.TestFixture WoofWare.NUnitTestRunner.TestFixture.Empty [static method]: System.Type -> WoofWare.NUnitTestRunner.ClassParallelScope WoofWare.NUnitTestRunner.Parallelizable option -> obj list list -> WoofWare.NUnitTestRunner.TestFixture
WoofWare.NUnitTestRunner.TestFixture.get_ContainingAssembly [method]: unit -> System.Reflection.Assembly WoofWare.NUnitTestRunner.TestFixture.get_ContainingAssembly [method]: unit -> System.Reflection.Assembly
WoofWare.NUnitTestRunner.TestFixture.get_Name [method]: unit -> string WoofWare.NUnitTestRunner.TestFixture.get_Name [method]: unit -> string
WoofWare.NUnitTestRunner.TestFixture.get_OneTimeSetUp [method]: unit -> System.Reflection.MethodInfo option WoofWare.NUnitTestRunner.TestFixture.get_OneTimeSetUp [method]: unit -> System.Reflection.MethodInfo option
WoofWare.NUnitTestRunner.TestFixture.get_OneTimeTearDown [method]: unit -> System.Reflection.MethodInfo option WoofWare.NUnitTestRunner.TestFixture.get_OneTimeTearDown [method]: unit -> System.Reflection.MethodInfo option
WoofWare.NUnitTestRunner.TestFixture.get_Parallelize [method]: unit -> WoofWare.NUnitTestRunner.ClassParallelScope WoofWare.NUnitTestRunner.Parallelizable option
WoofWare.NUnitTestRunner.TestFixture.get_Parameters [method]: unit -> obj list list
WoofWare.NUnitTestRunner.TestFixture.get_SetUp [method]: unit -> System.Reflection.MethodInfo list WoofWare.NUnitTestRunner.TestFixture.get_SetUp [method]: unit -> System.Reflection.MethodInfo list
WoofWare.NUnitTestRunner.TestFixture.get_TearDown [method]: unit -> System.Reflection.MethodInfo list WoofWare.NUnitTestRunner.TestFixture.get_TearDown [method]: unit -> System.Reflection.MethodInfo list
WoofWare.NUnitTestRunner.TestFixture.get_Tests [method]: unit -> WoofWare.NUnitTestRunner.SingleTestMethod list WoofWare.NUnitTestRunner.TestFixture.get_Tests [method]: unit -> WoofWare.NUnitTestRunner.SingleTestMethod list
WoofWare.NUnitTestRunner.TestFixture.get_Type [method]: unit -> System.Type
WoofWare.NUnitTestRunner.TestFixture.Name [property]: [read-only] string WoofWare.NUnitTestRunner.TestFixture.Name [property]: [read-only] string
WoofWare.NUnitTestRunner.TestFixture.OneTimeSetUp [property]: [read-only] System.Reflection.MethodInfo option WoofWare.NUnitTestRunner.TestFixture.OneTimeSetUp [property]: [read-only] System.Reflection.MethodInfo option
WoofWare.NUnitTestRunner.TestFixture.OneTimeTearDown [property]: [read-only] System.Reflection.MethodInfo option WoofWare.NUnitTestRunner.TestFixture.OneTimeTearDown [property]: [read-only] System.Reflection.MethodInfo option
WoofWare.NUnitTestRunner.TestFixture.Parallelize [property]: [read-only] WoofWare.NUnitTestRunner.ClassParallelScope WoofWare.NUnitTestRunner.Parallelizable option
WoofWare.NUnitTestRunner.TestFixture.Parameters [property]: [read-only] obj list list
WoofWare.NUnitTestRunner.TestFixture.SetUp [property]: [read-only] System.Reflection.MethodInfo list WoofWare.NUnitTestRunner.TestFixture.SetUp [property]: [read-only] System.Reflection.MethodInfo list
WoofWare.NUnitTestRunner.TestFixture.TearDown [property]: [read-only] System.Reflection.MethodInfo list WoofWare.NUnitTestRunner.TestFixture.TearDown [property]: [read-only] System.Reflection.MethodInfo list
WoofWare.NUnitTestRunner.TestFixture.Tests [property]: [read-only] WoofWare.NUnitTestRunner.SingleTestMethod list WoofWare.NUnitTestRunner.TestFixture.Tests [property]: [read-only] WoofWare.NUnitTestRunner.SingleTestMethod list
WoofWare.NUnitTestRunner.TestFixture.Type [property]: [read-only] System.Type
WoofWare.NUnitTestRunner.TestFixtureModule inherit obj WoofWare.NUnitTestRunner.TestFixtureModule inherit obj
WoofWare.NUnitTestRunner.TestFixtureModule.parse [static method]: System.Type -> WoofWare.NUnitTestRunner.TestFixture WoofWare.NUnitTestRunner.TestFixtureModule.parse [static method]: System.Type -> WoofWare.NUnitTestRunner.TestFixture
WoofWare.NUnitTestRunner.TestFixtureModule.run [static method]: WoofWare.NUnitTestRunner.ITestProgress -> (WoofWare.NUnitTestRunner.TestFixture -> WoofWare.NUnitTestRunner.SingleTestMethod -> bool) -> WoofWare.NUnitTestRunner.TestFixture -> WoofWare.NUnitTestRunner.FixtureRunResults WoofWare.NUnitTestRunner.TestFixtureModule.run [static method]: WoofWare.NUnitTestRunner.TestContexts -> WoofWare.NUnitTestRunner.ParallelQueue -> WoofWare.NUnitTestRunner.ITestProgress -> (WoofWare.NUnitTestRunner.TestFixture -> WoofWare.NUnitTestRunner.SingleTestMethod -> bool) -> WoofWare.NUnitTestRunner.TestFixture -> WoofWare.NUnitTestRunner.FixtureRunResults list System.Threading.Tasks.Task
WoofWare.NUnitTestRunner.TestFixtureModule.runOneFixture [static method]: WoofWare.NUnitTestRunner.TestContexts -> WoofWare.NUnitTestRunner.ParallelQueue -> WoofWare.NUnitTestRunner.ITestProgress -> (WoofWare.NUnitTestRunner.TestFixture -> WoofWare.NUnitTestRunner.SingleTestMethod -> bool) -> string -> obj -> WoofWare.NUnitTestRunner.TestFixture -> WoofWare.NUnitTestRunner.FixtureRunResults System.Threading.Tasks.Task
WoofWare.NUnitTestRunner.TestFixtureRunningToken inherit obj, implements WoofWare.NUnitTestRunner.TestFixtureRunningToken System.IEquatable, System.Collections.IStructuralEquatable
WoofWare.NUnitTestRunner.TestFixtureSetupToken inherit obj, implements WoofWare.NUnitTestRunner.TestFixtureSetupToken System.IEquatable, System.Collections.IStructuralEquatable
WoofWare.NUnitTestRunner.TestFixtureTearDownToken inherit obj, implements WoofWare.NUnitTestRunner.TestFixtureTearDownToken System.IEquatable, System.Collections.IStructuralEquatable
WoofWare.NUnitTestRunner.TestKind inherit obj, implements WoofWare.NUnitTestRunner.TestKind System.IEquatable, System.Collections.IStructuralEquatable - union type with 3 cases WoofWare.NUnitTestRunner.TestKind inherit obj, implements WoofWare.NUnitTestRunner.TestKind System.IEquatable, System.Collections.IStructuralEquatable - union type with 3 cases
WoofWare.NUnitTestRunner.TestKind+Data inherit WoofWare.NUnitTestRunner.TestKind WoofWare.NUnitTestRunner.TestKind+Data inherit WoofWare.NUnitTestRunner.TestKind
WoofWare.NUnitTestRunner.TestKind+Data.get_Item [method]: unit -> obj list list WoofWare.NUnitTestRunner.TestKind+Data.get_Item [method]: unit -> obj list list
@@ -277,6 +399,7 @@ WoofWare.NUnitTestRunner.TestMemberSuccess.Ok [static property]: [read-only] Woo
WoofWare.NUnitTestRunner.TestMemberSuccess.Tag [property]: [read-only] int WoofWare.NUnitTestRunner.TestMemberSuccess.Tag [property]: [read-only] int
WoofWare.NUnitTestRunner.TestProgress inherit obj WoofWare.NUnitTestRunner.TestProgress inherit obj
WoofWare.NUnitTestRunner.TestProgress.toStderr [static method]: unit -> WoofWare.NUnitTestRunner.ITestProgress WoofWare.NUnitTestRunner.TestProgress.toStderr [static method]: unit -> WoofWare.NUnitTestRunner.ITestProgress
WoofWare.NUnitTestRunner.TestProgress.toWriter [static method]: System.IO.TextWriter -> WoofWare.NUnitTestRunner.ITestProgress
WoofWare.NUnitTestRunner.TrxCounters inherit obj, implements WoofWare.NUnitTestRunner.TrxCounters System.IEquatable, System.Collections.IStructuralEquatable WoofWare.NUnitTestRunner.TrxCounters inherit obj, implements WoofWare.NUnitTestRunner.TrxCounters System.IEquatable, System.Collections.IStructuralEquatable
WoofWare.NUnitTestRunner.TrxCounters..ctor [constructor]: (System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32) WoofWare.NUnitTestRunner.TrxCounters..ctor [constructor]: (System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32, System.UInt32)
WoofWare.NUnitTestRunner.TrxCounters.Aborted [property]: [read-only] System.UInt32 WoofWare.NUnitTestRunner.TrxCounters.Aborted [property]: [read-only] System.UInt32
@@ -352,10 +475,12 @@ WoofWare.NUnitTestRunner.TrxOutcome.Parse [static method]: string -> WoofWare.NU
WoofWare.NUnitTestRunner.TrxOutcome.Tag [property]: [read-only] int WoofWare.NUnitTestRunner.TrxOutcome.Tag [property]: [read-only] int
WoofWare.NUnitTestRunner.TrxOutcome.Warning [static property]: [read-only] WoofWare.NUnitTestRunner.TrxOutcome WoofWare.NUnitTestRunner.TrxOutcome.Warning [static property]: [read-only] WoofWare.NUnitTestRunner.TrxOutcome
WoofWare.NUnitTestRunner.TrxOutput inherit obj, implements WoofWare.NUnitTestRunner.TrxOutput System.IEquatable, System.Collections.IStructuralEquatable WoofWare.NUnitTestRunner.TrxOutput inherit obj, implements WoofWare.NUnitTestRunner.TrxOutput System.IEquatable, System.Collections.IStructuralEquatable
WoofWare.NUnitTestRunner.TrxOutput..ctor [constructor]: (string option, WoofWare.NUnitTestRunner.TrxErrorInfo option) WoofWare.NUnitTestRunner.TrxOutput..ctor [constructor]: (string option, string option, WoofWare.NUnitTestRunner.TrxErrorInfo option)
WoofWare.NUnitTestRunner.TrxOutput.ErrorInfo [property]: [read-only] WoofWare.NUnitTestRunner.TrxErrorInfo option WoofWare.NUnitTestRunner.TrxOutput.ErrorInfo [property]: [read-only] WoofWare.NUnitTestRunner.TrxErrorInfo option
WoofWare.NUnitTestRunner.TrxOutput.get_ErrorInfo [method]: unit -> WoofWare.NUnitTestRunner.TrxErrorInfo option WoofWare.NUnitTestRunner.TrxOutput.get_ErrorInfo [method]: unit -> WoofWare.NUnitTestRunner.TrxErrorInfo option
WoofWare.NUnitTestRunner.TrxOutput.get_StdErr [method]: unit -> string option
WoofWare.NUnitTestRunner.TrxOutput.get_StdOut [method]: unit -> string option WoofWare.NUnitTestRunner.TrxOutput.get_StdOut [method]: unit -> string option
WoofWare.NUnitTestRunner.TrxOutput.StdErr [property]: [read-only] string option
WoofWare.NUnitTestRunner.TrxOutput.StdOut [property]: [read-only] string option WoofWare.NUnitTestRunner.TrxOutput.StdOut [property]: [read-only] string option
WoofWare.NUnitTestRunner.TrxReport inherit obj, implements WoofWare.NUnitTestRunner.TrxReport System.IEquatable, System.Collections.IStructuralEquatable WoofWare.NUnitTestRunner.TrxReport inherit obj, implements WoofWare.NUnitTestRunner.TrxReport System.IEquatable, System.Collections.IStructuralEquatable
WoofWare.NUnitTestRunner.TrxReport..ctor [constructor]: (System.Guid, string, WoofWare.NUnitTestRunner.TrxReportTimes, WoofWare.NUnitTestRunner.TrxTestSettings, WoofWare.NUnitTestRunner.TrxUnitTestResult list, WoofWare.NUnitTestRunner.TrxUnitTest list, WoofWare.NUnitTestRunner.TrxTestEntry list, WoofWare.NUnitTestRunner.TrxTestListEntry list, WoofWare.NUnitTestRunner.TrxResultsSummary) WoofWare.NUnitTestRunner.TrxReport..ctor [constructor]: (System.Guid, string, WoofWare.NUnitTestRunner.TrxReportTimes, WoofWare.NUnitTestRunner.TrxTestSettings, WoofWare.NUnitTestRunner.TrxUnitTestResult list, WoofWare.NUnitTestRunner.TrxUnitTest list, WoofWare.NUnitTestRunner.TrxTestEntry list, WoofWare.NUnitTestRunner.TrxTestListEntry list, WoofWare.NUnitTestRunner.TrxResultsSummary)

View File

@@ -1,25 +1,14 @@
namespace WoofWare.NUnitTestRunner namespace WoofWare.NUnitTestRunner
open System open System
open System.Collections
open System.Diagnostics open System.Diagnostics
open System.IO open System.IO
open System.Reflection open System.Reflection
open System.Threading open System.Threading
open System.Threading.Tasks
open Microsoft.FSharp.Core open Microsoft.FSharp.Core
type private StdoutSetter (newStdout : StreamWriter, newStderr : StreamWriter) =
let oldStdout = Console.Out
let oldStderr = Console.Error
do
Console.SetOut newStdout
Console.SetError newStderr
interface IDisposable with
member _.Dispose () =
Console.SetOut oldStdout
Console.SetError oldStderr
/// Information about the circumstances of a run of a single test. /// Information about the circumstances of a run of a single test.
type IndividualTestRunMetadata = type IndividualTestRunMetadata =
{ {
@@ -79,6 +68,8 @@ module TestFixture =
/// ///
/// This function does not throw. /// This function does not throw.
let private runOne let private runOne
(outputId : OutputStreamId)
(contexts : TestContexts)
(setUp : MethodInfo list) (setUp : MethodInfo list)
(tearDown : MethodInfo list) (tearDown : MethodInfo list)
(testId : Guid) (testId : Guid)
@@ -111,13 +102,6 @@ module TestFixture =
let start = DateTimeOffset.Now let start = DateTimeOffset.Now
use stdOutStream = new MemoryStream ()
use stdErrStream = new MemoryStream ()
use stdOut = new StreamWriter (stdOutStream)
use stdErr = new StreamWriter (stdErrStream)
use _ = new StdoutSetter (stdOut, stdErr)
let sw = Stopwatch.StartNew () let sw = Stopwatch.StartNew ()
let metadata () = let metadata () =
@@ -138,13 +122,13 @@ module TestFixture =
TestName = name TestName = name
ClassName = test.DeclaringType.FullName ClassName = test.DeclaringType.FullName
StdOut = StdOut =
match stdOutStream.ToArray () with match contexts.DumpStdout outputId with
| [||] -> None | "" -> None
| arr -> Console.OutputEncoding.GetString arr |> Some | v -> Some v
StdErr = StdErr =
match stdErrStream.ToArray () with match contexts.DumpStderr outputId with
| [||] -> None | "" -> None
| arr -> Console.OutputEncoding.GetString arr |> Some | v -> Some v
} }
let setUpResult = runMethods TestFailure.SetUpFailed setUp [||] let setUpResult = runMethods TestFailure.SetUpFailed setUp [||]
@@ -168,8 +152,6 @@ module TestFixture =
| "NUnit.Framework.IgnoreException" -> Ok (Some (TestMemberSuccess.Ignored (Option.ofObj exc.Message))) | "NUnit.Framework.IgnoreException" -> Ok (Some (TestMemberSuccess.Ignored (Option.ofObj exc.Message)))
| "NUnit.Framework.InconclusiveException" -> | "NUnit.Framework.InconclusiveException" ->
Ok (Some (TestMemberSuccess.Inconclusive (Option.ofObj exc.Message))) Ok (Some (TestMemberSuccess.Inconclusive (Option.ofObj exc.Message)))
| s when s.StartsWith ("NUnit.Framework.", StringComparison.Ordinal) ->
failwith $"Unrecognised special exception: %s{s}"
| _ -> Error orig | _ -> Error orig
| Error orig -> Error orig | Error orig -> Error orig
@@ -224,11 +206,15 @@ module TestFixture =
/// This method should never throw: it only throws if there's a critical logic error in the runner. /// This method should never throw: it only throws if there's a critical logic error in the runner.
/// Exceptions from the units under test are wrapped up and passed out. /// Exceptions from the units under test are wrapped up and passed out.
let private runTestsFromMember let private runTestsFromMember
(contexts : TestContexts)
(par : ParallelQueue)
(running : TestFixtureSetupToken)
(progress : ITestProgress)
(setUp : MethodInfo list) (setUp : MethodInfo list)
(tearDown : MethodInfo list) (tearDown : MethodInfo list)
(containingObject : obj) (containingObject : obj)
(test : SingleTestMethod) (test : SingleTestMethod)
: (Result<TestMemberSuccess, TestMemberFailure> * IndividualTestRunMetadata) list : (Result<TestMemberSuccess, TestMemberFailure> * IndividualTestRunMetadata) Task list
= =
if test.Method.ContainsGenericParameters then if test.Method.ContainsGenericParameters then
let failureMetadata = let failureMetadata =
@@ -248,7 +234,7 @@ module TestFixture =
let error = let error =
TestMemberFailure.Malformed [ "Test contained generic parameters; generics are not supported." ] TestMemberFailure.Malformed [ "Test contained generic parameters; generics are not supported." ]
(Error error, failureMetadata) |> List.singleton (Error error, failureMetadata) |> Task.FromResult |> List.singleton
else else
let resultPreRun = let resultPreRun =
@@ -264,17 +250,12 @@ module TestFixture =
| Modifier.Ignored reason -> Some (TestMemberSuccess.Ignored reason) | Modifier.Ignored reason -> Some (TestMemberSuccess.Ignored reason)
) )
let sw = Stopwatch.StartNew ()
let startTime = DateTimeOffset.Now
match resultPreRun with match resultPreRun with
| Some result -> | Some result ->
sw.Stop ()
let failureMetadata = let failureMetadata =
{ {
Total = sw.Elapsed Total = TimeSpan.Zero
Start = startTime Start = DateTimeOffset.Now
End = DateTimeOffset.Now End = DateTimeOffset.Now
ComputerName = Environment.MachineName ComputerName = Environment.MachineName
ExecutionId = Guid.NewGuid () ExecutionId = Guid.NewGuid ()
@@ -286,7 +267,7 @@ module TestFixture =
StdOut = None StdOut = None
} }
[ Ok result, failureMetadata ] (Ok result, failureMetadata) |> Task.FromResult |> List.singleton
| None -> | None ->
let individualTests = let individualTests =
@@ -349,7 +330,7 @@ module TestFixture =
// Might not be an IEnumerable of a reference type. // Might not be an IEnumerable of a reference type.
// Concretely, `FSharpList<HttpStatusCode> :> IEnumerable<obj>` fails. // Concretely, `FSharpList<HttpStatusCode> :> IEnumerable<obj>` fails.
for arg in args.GetValue (null : obj) :?> System.Collections.IEnumerable do for arg in args.GetValue (null : obj) :?> IEnumerable do
yield yield
Guid.NewGuid (), Guid.NewGuid (),
match arg with match arg with
@@ -372,20 +353,19 @@ module TestFixture =
if isNull argsMem then if isNull argsMem then
failwith "Unexpectedly could not call `.Arguments` on TestCaseData" failwith "Unexpectedly could not call `.Arguments` on TestCaseData"
// TODO: need to capture this stdout/stderr
(argsMem.Invoke (arg, [||]) |> unbox<obj[]>) (argsMem.Invoke (arg, [||]) |> unbox<obj[]>)
else else
[| arg |] [| arg |]
] ]
|> Ok |> Ok
sw.Stop ()
match individualTests with match individualTests with
| Error e -> | Error e ->
let failureMetadata = let failureMetadata =
{ {
Total = sw.Elapsed Total = TimeSpan.Zero
Start = startTime Start = DateTimeOffset.Now
End = DateTimeOffset.Now End = DateTimeOffset.Now
ComputerName = Environment.MachineName ComputerName = Environment.MachineName
ExecutionId = Guid.NewGuid () ExecutionId = Guid.NewGuid ()
@@ -398,7 +378,7 @@ module TestFixture =
StdOut = None StdOut = None
} }
[ Error e, failureMetadata ] (Error e, failureMetadata) |> Task.FromResult |> List.singleton
| Ok individualTests -> | Ok individualTests ->
let count = test.Repeat |> Option.defaultValue 1 let count = test.Repeat |> Option.defaultValue 1
@@ -406,149 +386,256 @@ module TestFixture =
Seq.init count (fun _ -> individualTests) Seq.init count (fun _ -> individualTests)
|> Seq.concat |> Seq.concat
|> Seq.map (fun (testGuid, args) -> |> Seq.map (fun (testGuid, args) ->
let results, summary = task {
runOne setUp tearDown testGuid test.Method containingObject args let runMe () =
progress.OnTestMemberStart test.Name
let oldValue = contexts.AsyncLocal.Value
let outputId = contexts.NewOutputs ()
contexts.AsyncLocal.Value <- outputId
match results with let result, meta =
| Ok results -> Ok results, summary runOne outputId contexts setUp tearDown testGuid test.Method containingObject args
| Error e -> Error (TestMemberFailure.Failed e), summary
contexts.AsyncLocal.Value <- oldValue
progress.OnTestMemberFinished test.Name
result, meta
let! results, summary = par.Run running test.Parallelize runMe
match results with
| Ok results -> return Ok results, summary
| Error e -> return Error (TestMemberFailure.Failed e), summary
}
) )
|> Seq.toList |> Seq.toList
/// Run every test (except those which fail the `filter`) in this test fixture, as well as the /// Run every test (except those which fail the `filter`) in this test fixture, as well as the
/// appropriate setup and tear-down logic. /// appropriate setup and tear-down logic.
let run let runOneFixture
(contexts : TestContexts)
(par : ParallelQueue)
(progress : ITestProgress) (progress : ITestProgress)
(filter : TestFixture -> SingleTestMethod -> bool) (filter : TestFixture -> SingleTestMethod -> bool)
(name : string)
(containingObject : obj)
(tests : TestFixture) (tests : TestFixture)
: FixtureRunResults : FixtureRunResults Task
= =
progress.OnTestFixtureStart tests.Name tests.Tests.Length task {
let! running = par.StartTestFixture tests
progress.OnTestFixtureStart name tests.Tests.Length
let containingObject = let oldWorkDir = Environment.CurrentDirectory
let methods = Environment.CurrentDirectory <- FileInfo(tests.ContainingAssembly.Location).Directory.FullName
seq {
match tests.OneTimeSetUp with
| None -> ()
| Some t -> yield t
match tests.OneTimeTearDown with let sw = Stopwatch.StartNew ()
| None -> () let startTime = DateTimeOffset.UtcNow
| Some t -> yield t
yield! tests.Tests |> Seq.map (fun t -> t.Method) let endMetadata (outputId : OutputStreamId) =
let stdOut = contexts.DumpStdout outputId
let stdErr = contexts.DumpStderr outputId
{
Total = sw.Elapsed
Start = startTime
End = DateTimeOffset.UtcNow
ComputerName = Environment.MachineName
ExecutionId = Guid.NewGuid ()
TestId = Guid.NewGuid ()
// This one is a bit dubious, because we don't actually have a test name at all
TestName = name
ClassName = tests.Name
StdOut = if String.IsNullOrEmpty stdOut then None else Some stdOut
StdErr = if String.IsNullOrEmpty stdErr then None else Some stdErr
} }
methods let! setupResult, running =
|> Seq.tryPick (fun mi -> match tests.OneTimeSetUp with
if not mi.IsStatic then | Some su ->
Some (Activator.CreateInstance mi.DeclaringType) par.RunTestSetup
else running
None (fun () ->
) let oldValue = contexts.AsyncLocal.Value
|> Option.toObj let newOutputs = contexts.NewOutputs ()
contexts.AsyncLocal.Value <- newOutputs
let oldWorkDir = Environment.CurrentDirectory let result =
Environment.CurrentDirectory <- FileInfo(tests.ContainingAssembly.Location).Directory.FullName try
match su.Invoke (containingObject, [||]) with
| :? unit -> None
| ret ->
Some (UserMethodFailure.ReturnedNonUnit (su.Name, ret), endMetadata newOutputs)
with :? TargetInvocationException as e ->
Some (UserMethodFailure.Threw (su.Name, e.InnerException), endMetadata newOutputs)
let sw = Stopwatch.StartNew () contexts.AsyncLocal.Value <- oldValue
let startTime = DateTimeOffset.UtcNow
use stdOutStream = new MemoryStream () match result with
use stdOut = new StreamWriter (stdOutStream) | None -> Ok (Some newOutputs)
use stdErrStream = new MemoryStream () | Some err -> Error (err, newOutputs)
use stdErr = new StreamWriter (stdErrStream) )
use _ = new StdoutSetter (stdOut, stdErr) | _ -> Task.FromResult (Ok None, TestFixtureSetupToken.vouchNoSetupRequired running)
let endMetadata () = let testFailures = ResizeArray<TestMemberFailure * IndividualTestRunMetadata> ()
let stdOut = stdOutStream.ToArray () |> Console.OutputEncoding.GetString
let stdErr = stdErrStream.ToArray () |> Console.OutputEncoding.GetString
{ let successes =
Total = sw.Elapsed ResizeArray<SingleTestMethod * TestMemberSuccess * IndividualTestRunMetadata> ()
Start = startTime
End = DateTimeOffset.UtcNow
ComputerName = Environment.MachineName
ExecutionId = Guid.NewGuid ()
TestId = Guid.NewGuid ()
// This one is a bit dubious, because we don't actually have a test name at all
TestName = tests.Name
ClassName = tests.Name
StdOut = if String.IsNullOrEmpty stdOut then None else Some stdOut
StdErr = if String.IsNullOrEmpty stdErr then None else Some stdErr
}
let setupResult = let testsRun =
match tests.OneTimeSetUp with match setupResult with
| Some su -> | Error _ ->
try // Don't run any tests if setup failed.
match su.Invoke (containingObject, [||]) with Task.FromResult ()
| :? unit -> None | Ok _ ->
| ret -> Some (UserMethodFailure.ReturnedNonUnit (su.Name, ret), endMetadata ()) tests.Tests
with :? TargetInvocationException as e -> |> Seq.filter (fun test ->
Some (UserMethodFailure.Threw (su.Name, e.InnerException), endMetadata ()) if filter tests test then
| _ -> None true
else
progress.OnTestMemberSkipped test.Name
false
)
|> Seq.map (fun test ->
task {
let testSuccess = ref 0
let testFailures = ResizeArray<TestMemberFailure * IndividualTestRunMetadata> () let results =
runTestsFromMember
contexts
par
running
progress
tests.SetUp
tests.TearDown
containingObject
test
let successes = let! result =
ResizeArray<SingleTestMethod * TestMemberSuccess * IndividualTestRunMetadata> () results
|> List.map (fun t ->
task {
let! result, report = t
match setupResult with match result with
| Some _ -> | Error failure ->
// Don't run any tests if setup failed. testFailures.Add (failure, report)
() progress.OnTestFailed test.Name failure
| None -> | Ok result ->
for test in tests.Tests do Interlocked.Increment testSuccess |> ignore<int>
if filter tests test then lock successes (fun () -> successes.Add (test, result, report))
progress.OnTestMemberStart test.Name }
let testSuccess = ref 0 )
|> Task.WhenAll
let results = runTestsFromMember tests.SetUp tests.TearDown containingObject test result |> Array.iter id
}
)
|> Task.WhenAll
|> fun t ->
task {
let! t = t
return t |> Array.iter id
}
for result, report in results do do! testsRun
match result with
| Error failure ->
testFailures.Add (failure, report)
progress.OnTestFailed test.Name failure
| Ok result ->
Interlocked.Increment testSuccess |> ignore<int>
lock successes (fun () -> successes.Add (test, result, report))
progress.OnTestMemberFinished test.Name // Unconditionally run OneTimeTearDown if it exists.
else let! tearDownError, tornDown =
progress.OnTestMemberSkipped test.Name match tests.OneTimeTearDown with
| Some td ->
par.RunTestTearDown
running
(fun () ->
let oldValue = contexts.AsyncLocal.Value
let outputs = contexts.NewOutputs ()
contexts.AsyncLocal.Value <- outputs
// Unconditionally run OneTimeTearDown if it exists. let result =
let tearDownError = try
match tests.OneTimeTearDown with match td.Invoke (containingObject, [||]) with
| Some td -> | :? unit -> None
try | ret ->
match td.Invoke (containingObject, [||]) with Some (UserMethodFailure.ReturnedNonUnit (td.Name, ret), endMetadata outputs)
| null -> None with :? TargetInvocationException as e ->
| ret -> Some (UserMethodFailure.ReturnedNonUnit (td.Name, ret), endMetadata ()) Some (UserMethodFailure.Threw (td.Name, e.InnerException), endMetadata outputs)
with :? TargetInvocationException as e ->
Some (UserMethodFailure.Threw (td.Name, e), endMetadata ())
| _ -> None
Environment.CurrentDirectory <- oldWorkDir contexts.AsyncLocal.Value <- oldValue
{ match result with
Failed = testFailures |> Seq.toList | None -> Ok (Some outputs)
Success = successes |> Seq.toList | Some err -> Error (err, outputs)
OtherFailures = [ tearDownError ; setupResult ] |> List.choose id )
| _ -> Task.FromResult (Ok None, TestFixtureTearDownToken.vouchNoTearDownRequired running)
Environment.CurrentDirectory <- oldWorkDir
do! par.EndTestFixture tornDown
// TODO: we have access to stdout/err of OneTimeSetUp and OneTimeTearDown here, but we throw them away.
return
{
Failed = testFailures |> Seq.toList
Success = successes |> Seq.toList
OtherFailures =
[ tearDownError ; setupResult ]
|> List.choose (
function
| Error (e, _) -> Some e
| Ok _ -> None
)
}
} }
/// Interpret this type as a [<TestFixture>], extracting the test members from it and annotating them with all /// Interpret this type as a [<TestFixture>], extracting the test members from it and annotating them with all
/// relevant information about how we should run them. /// relevant information about how we should run them.
let parse (parentType : Type) : TestFixture = let parse (parentType : Type) : TestFixture =
let categories = let categories, args, par =
parentType.CustomAttributes (([], [], None), parentType.CustomAttributes)
|> Seq.filter (fun attr -> attr.AttributeType.FullName = "NUnit.Framework.CategoryAttribute") ||> Seq.fold (fun (categories, args, par) attr ->
|> Seq.map (fun attr -> attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox<string>) match attr.AttributeType.FullName with
|> Seq.toList | "NUnit.Framework.SetUpFixtureAttribute" ->
failwith "This test runner does not support SetUpFixture. Please shout if you want this."
| "NUnit.Framework.CategoryAttribute" ->
let cat = attr.ConstructorArguments |> Seq.exactlyOne |> _.Value |> unbox<string>
cat :: categories, args, par
| "NUnit.Framework.TestFixtureAttribute" ->
let newArgs =
match attr.ConstructorArguments |> Seq.map _.Value |> Seq.toList with
| [ :? ICollection as x ] ->
x |> Seq.cast<CustomAttributeTypedArgument> |> Seq.map _.Value |> Seq.toList
| xs -> xs
(TestFixture.Empty parentType.Assembly parentType.Name, parentType.GetRuntimeMethods ()) categories, newArgs :: args, par
| "NUnit.Framework.NonParallelizableAttribute" ->
match par with
| Some _ -> failwith $"Got multiple parallelism attributes on %s{parentType.FullName}"
| None -> categories, args, Some Parallelizable.No
| "NUnit.Framework.ParallelizableAttribute" ->
match par with
| Some _ -> failwith $"Got multiple parallelism attributes on %s{parentType.FullName}"
| None ->
match attr.ConstructorArguments |> Seq.toList with
| [] -> categories, args, Some (Parallelizable.Yes ClassParallelScope.Self)
| [ v ] ->
match v.Value with
| :? int as v ->
match v with
| 512 -> categories, args, Some (Parallelizable.Yes ClassParallelScope.Fixtures)
| 256 -> categories, args, Some (Parallelizable.Yes ClassParallelScope.Children)
| 257 -> categories, args, Some (Parallelizable.Yes ClassParallelScope.All)
| 1 -> categories, args, Some (Parallelizable.Yes ClassParallelScope.Self)
| v ->
failwith
$"Could not recognise value %i{v} of parallel scope in %s{parentType.FullName}"
| v ->
failwith
$"Unexpectedly non-int value %O{v} of parallel scope in %s{parentType.FullName}"
| _ -> failwith $"unexpectedly got multiple args to Parallelizable on %s{parentType.FullName}"
| _ -> categories, args, par
)
(TestFixture.Empty parentType par args, parentType.GetRuntimeMethods ())
||> Seq.fold (fun state mi -> ||> Seq.fold (fun state mi ->
((state, []), mi.CustomAttributes) ((state, []), mi.CustomAttributes)
||> Seq.fold (fun (state, unrecognisedAttrs) attr -> ||> Seq.fold (fun (state, unrecognisedAttrs) attr ->
@@ -618,3 +705,56 @@ module TestFixture =
state state
) )
/// Run every test (except those which fail the `filter`) in this test fixture, as well as the
/// appropriate setup and tear-down logic.
let run
(contexts : TestContexts)
(par : ParallelQueue)
(progress : ITestProgress)
(filter : TestFixture -> SingleTestMethod -> bool)
(tests : TestFixture)
: FixtureRunResults list Task
=
match tests.Parameters with
| [] -> [ null ]
| args -> args |> List.map List.toArray
|> List.map (fun args ->
let containingObject =
let methods =
seq {
match tests.OneTimeSetUp with
| None -> ()
| Some t -> yield t
match tests.OneTimeTearDown with
| None -> ()
| Some t -> yield t
yield! tests.Tests |> Seq.map (fun t -> t.Method)
}
methods
|> Seq.tryPick (fun mi ->
if not mi.IsStatic then
Some (Activator.CreateInstance (mi.DeclaringType, args))
else
None
)
|> Option.toObj
let name =
if isNull args then
tests.Name
else
let args = args |> Seq.map (fun o -> o.ToString ()) |> String.concat ","
$"%s{tests.Name}(%s{args})"
runOneFixture contexts par progress filter name containingObject tests
)
|> Task.WhenAll
|> fun t ->
task {
let! t = t
return Array.toList t
}

View File

@@ -1,6 +1,7 @@
namespace WoofWare.NUnitTestRunner namespace WoofWare.NUnitTestRunner
open System open System
open System.IO
/// Represents something which knows how to report progress through a test suite. /// Represents something which knows how to report progress through a test suite.
/// Note that we don't guarantee anything about parallelism; you must make sure /// Note that we don't guarantee anything about parallelism; you must make sure
@@ -24,22 +25,25 @@ type ITestProgress =
/// Methods for constructing specific ITestProgress objects. /// Methods for constructing specific ITestProgress objects.
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module TestProgress = module TestProgress =
/// An ITestProgress which logs to stderr. /// An ITestProgress which logs to the given writer.
let toStderr () : ITestProgress = let toWriter (writer : TextWriter) : ITestProgress =
{ new ITestProgress with { new ITestProgress with
member _.OnTestFixtureStart name testCount = member _.OnTestFixtureStart name testCount =
let plural = if testCount = 1 then "" else "s" let plural = if testCount = 1 then "" else "s"
Console.Error.WriteLine $"Running test fixture: %s{name} (%i{testCount} test%s{plural} to run)" writer.WriteLine $"Running test fixture: %s{name} (%i{testCount} test%s{plural} to run)"
member _.OnTestMemberStart name = member _.OnTestMemberStart name =
Console.Error.WriteLine $"Running test: %s{name}" writer.WriteLine $"Running test: %s{name}"
member _.OnTestFailed name failure = member _.OnTestFailed name failure =
Console.Error.WriteLine $"Test failed: %O{failure}" writer.WriteLine $"Test failed: %O{failure}"
member _.OnTestMemberFinished name = member _.OnTestMemberFinished name =
Console.Error.WriteLine $"Finished test %s{name}" writer.WriteLine $"Finished test %s{name}"
member _.OnTestMemberSkipped name = member _.OnTestMemberSkipped name =
Console.Error.WriteLine $"Skipping test due to filter: %s{name}" writer.WriteLine $"Skipping test due to filter: %s{name}"
} }
/// An ITestProgress which logs to stderr.
let toStderr () : ITestProgress = toWriter Console.Error

View File

@@ -290,6 +290,8 @@ type TrxOutput =
{ {
/// What the entity printed to standard output. /// What the entity printed to standard output.
StdOut : string option StdOut : string option
/// What the entity printed to standard error.
StdErr : string option
/// Description of any error the entity encountered. /// Description of any error the entity encountered.
ErrorInfo : TrxErrorInfo option ErrorInfo : TrxErrorInfo option
} }
@@ -305,6 +307,14 @@ type TrxOutput =
childNode.AppendChild text |> ignore<XmlNode> childNode.AppendChild text |> ignore<XmlNode>
node.AppendChild childNode |> ignore<XmlNode> node.AppendChild childNode |> ignore<XmlNode>
match this.StdErr with
| None -> ()
| Some stderr ->
let text = doc.CreateTextNode stderr
let childNode = doc.CreateElement ("StdErr", XmlUtil.NS)
childNode.AppendChild text |> ignore<XmlNode>
node.AppendChild childNode |> ignore<XmlNode>
match this.ErrorInfo with match this.ErrorInfo with
| None -> () | None -> ()
| Some errInfo -> node.AppendChild (errInfo.toXml doc) |> ignore<XmlNode> | Some errInfo -> node.AppendChild (errInfo.toXml doc) |> ignore<XmlNode>
@@ -317,6 +327,11 @@ type TrxOutput =
| NodeWithNamedChild "StdOut" (OneChildNode "StdOut" (NoChildrenNode stdout)) -> Some stdout | NodeWithNamedChild "StdOut" (OneChildNode "StdOut" (NoChildrenNode stdout)) -> Some stdout
| _ -> None | _ -> None
let stderr =
match node with
| NodeWithNamedChild "StdErr" (OneChildNode "StdErr" (NoChildrenNode stdout)) -> Some stdout
| _ -> None
let errorInfo = let errorInfo =
match node with match node with
| NodeWithNamedChild "ErrorInfo" node -> | NodeWithNamedChild "ErrorInfo" node ->
@@ -330,6 +345,7 @@ type TrxOutput =
| Ok errorInfo -> | Ok errorInfo ->
{ {
StdOut = stdout StdOut = stdout
StdErr = stderr
ErrorInfo = errorInfo ErrorInfo = errorInfo
} }
|> Ok |> Ok

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<Authors>Patrick Stevens</Authors> <Authors>Patrick Stevens</Authors>
<Copyright>Copyright (c) Patrick Stevens 2024</Copyright> <Copyright>Copyright (c) Patrick Stevens 2024</Copyright>
@@ -14,20 +14,31 @@
<PackageId>WoofWare.NUnitTestRunner.Lib</PackageId> <PackageId>WoofWare.NUnitTestRunner.Lib</PackageId>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarnOn>FS3559</WarnOn> <WarnOn>FS3559</WarnOn>
<WoofWareMyriadPluginVersion>2.1.52</WoofWareMyriadPluginVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="AssemblyInfo.fs" /> <Compile Include="AssemblyInfo.fs" />
<Compile Include="RuntimeConfig.fs" />
<Compile Include="GeneratedRuntimeConfig.fs">
<MyriadFile>RuntimeConfig.fs</MyriadFile>
</Compile>
<Compile Include="DotnetRuntime.fs" />
<Compile Include="Array.fs" /> <Compile Include="Array.fs" />
<Compile Include="List.fs" /> <Compile Include="List.fs" />
<Compile Include="Result.fs" /> <Compile Include="Result.fs" />
<Compile Include="Domain.fs" /> <Compile Include="Domain.fs" />
<Compile Include="Filter.fs" /> <Compile Include="Filter.fs" />
<Compile Include="Args.fs" />
<Compile Include="ParallelQueue.fs" />
<Compile Include="SingleTestMethod.fs" /> <Compile Include="SingleTestMethod.fs" />
<Compile Include="TestProgress.fs" /> <Compile Include="TestProgress.fs" />
<Compile Include="Context.fs" />
<Compile Include="TestFixture.fs" /> <Compile Include="TestFixture.fs" />
<Compile Include="Xml.fs" /> <Compile Include="Xml.fs" />
<Compile Include="TrxReport.fs" /> <Compile Include="TrxReport.fs" />
<Compile Include="CreateTrxReport.fs" />
<Compile Include="AssemblyLevelAttributes.fs" />
<None Include="..\README.md"> <None Include="..\README.md">
<Pack>True</Pack> <Pack>True</Pack>
<PackagePath>\</PackagePath> <PackagePath>\</PackagePath>
@@ -36,8 +47,16 @@
<EmbeddedResource Include="version.json" /> <EmbeddedResource Include="version.json" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="WoofWare.PrattParser" Version="0.1.2" /> <PackageReference Include="WoofWare.PrattParser" Version="0.2.2" />
<PackageReference Update="FSharp.Core" Version="6.0.0" /> <PackageReference Update="FSharp.Core" Version="6.0.0" />
<PackageReference Include="WoofWare.DotnetRuntimeLocator" Version="0.1.9" />
<PackageReference Include="WoofWare.Myriad.Plugins.Attributes" Version="3.1.9" />
<PackageReference Include="Myriad.SDK" Version="0.8.3" PrivateAssets="all" />
<PackageReference Include="WoofWare.Myriad.Plugins" Version="$(WoofWareMyriadPluginVersion)" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<MyriadSdkGenerator Include="$(NuGetPackageRoot)/woofware.myriad.plugins/$(WoofWareMyriadPluginVersion)/lib/net6.0/WoofWare.Myriad.Plugins.dll" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

View File

@@ -1,5 +1,5 @@
{ {
"version": "0.8", "version": "0.16",
"publicReleaseRefSpec": [ "publicReleaseRefSpec": [
"^refs/heads/main$" "^refs/heads/main$"
], ],
@@ -8,4 +8,4 @@
":/Directory.Build.props", ":/Directory.Build.props",
":/README.md" ":/README.md"
] ]
} }

View File

@@ -0,0 +1,35 @@
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using WoofWare.NUnitTestRunner.StartupHook;
namespace WoofWare.NUnitTestRunner.StartupHook
{
internal class StartupAssemblyLoadContext : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver _resolver;
public StartupAssemblyLoadContext()
{
_resolver = new AssemblyDependencyResolver(Assembly.GetExecutingAssembly().Location);
}
protected override Assembly Load(AssemblyName assemblyName)
{
var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
return assemblyPath != null ? LoadFromAssemblyPath(assemblyPath) : null;
}
}
}
// Must be internal and called `StartupHook`
internal class StartupHook
{
public static void Initialize()
{
var loadContext = new StartupAssemblyLoadContext();
var assembly = loadContext.LoadFromAssemblyName(new AssemblyName("WoofWare.NUnitTestRunner.StartupHookLogic"));
assembly.DefinedTypes.First(x => x.Name == "StartupHookLogic").GetDeclaredMethod("DoIt")!.Invoke(null, null);
}
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\WoofWare.NUnitTestRunner.StartupHookLogic\WoofWare.NUnitTestRunner.StartupHookLogic.csproj" />
<ProjectReference Include="..\WoofWare.NUnitTestRunner.Lib\WoofWare.NUnitTestRunner.Lib.fsproj"/>
</ItemGroup>
<ItemGroup>
<Compile Include="StartupHook.cs"/>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,102 @@
using System.Reflection;
using Microsoft.FSharp.Collections;
using Microsoft.FSharp.Core;
namespace WoofWare.NUnitTestRunner.StartupHookLogic;
public class StartupHookLogic
{
private static void DoIt()
{
// Load test runner lib
var nunitLib = Environment.GetEnvironmentVariable("WOOFWARE_NUNIT_LIB");
if (string.IsNullOrEmpty(nunitLib))
{
throw new ArgumentException("WoofWare.NUnitTestRunner hook expects to run with WOOFWARE_NUNIT_LIB set to WoofWare.NUnitTestRunner.Lib.dll");
}
Assembly.LoadFrom(nunitLib);
var startTime = DateTimeOffset.Now;
// Arg parsing
var filterVar = Environment.GetEnvironmentVariable("WOOFWARE_NUNIT_FILTER");
static bool NoFilter(TestFixture f, SingleTestMethod g)
{
return true;
}
FSharpFunc<TestFixture, FSharpFunc<SingleTestMethod, bool>> filter;
if (string.IsNullOrEmpty(filterVar))
{
filter = FuncConvert.FromFunc<TestFixture, SingleTestMethod, bool>(NoFilter);
}
else
{
filter = FilterModule.shouldRun(FilterModule.parse(filterVar));
}
var assy = Assembly.GetEntryAssembly()!;
var attrs = AssemblyLevelAttributesModule.get(assy);
FSharpOption<int> levelOfParallelism;
var parallelismVar = Environment.GetEnvironmentVariable("WOOFWARE_NUNIT_PARALLELISM_LEVEL");
if (string.IsNullOrEmpty(parallelismVar))
{
levelOfParallelism = attrs.Parallelism;
}
else
{
levelOfParallelism = FSharpOption<int>.Some(Int32.Parse(parallelismVar));
}
var testFixtures = assy.ExportedTypes.Select(TestFixtureModule.parse);
using var par =
new ParallelQueue(levelOfParallelism, attrs.Parallelizable, FSharpOption<CancellationToken>.None);
var creationTime = DateTimeOffset.Now;
var timeoutVar = Environment.GetEnvironmentVariable("WOOFWARE_NUNIT_TIMEOUT_SECS");
TimeSpan timeout;
if (string.IsNullOrEmpty(timeoutVar))
{
timeout = TimeSpan.FromHours(2.0);
}
else
{
timeout = TimeSpan.FromSeconds(Int32.Parse(timeoutVar));
}
var normalErr = Console.Error;
using var contexts = TestContexts.Empty();
Console.SetOut(contexts.Stdout);
Console.SetError(contexts.Stderr);
var results =
Task.WhenAll(testFixtures.Select(x =>
TestFixtureModule.run(contexts, par, TestProgress.toWriter(normalErr), filter, x)));
if (!results.Wait(timeout))
{
throw new Exception($"Tests failed to terminate within timeout of {timeout}");
}
var sorted =
results.Result.SelectMany(x => x);
var report = BuildTrxReport.build(assy, creationTime, startTime, ListModule.OfSeq(sorted));
var trxFile = Environment.GetEnvironmentVariable("WOOFWARE_NUNIT_GENERATE_TRX");
if (!string.IsNullOrEmpty(trxFile))
{
var contents = TrxReportModule.toXml(report).OuterXml;
var trxPath = new FileInfo(trxFile);
trxPath.Directory!.Create();
File.WriteAllText(trxPath.FullName, contents);
normalErr.WriteLine($"Written TRX file: {trxPath.FullName}");
}
if (report.ResultsSummary.Outcome.Equals(TrxOutcome.Completed))
Environment.Exit(0);
else
Environment.Exit(1);
}
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\WoofWare.NUnitTestRunner.Lib\WoofWare.NUnitTestRunner.Lib.fsproj"/>
</ItemGroup>
<ItemGroup>
<Compile Include="StartupHookLogic.cs"/>
</ItemGroup>
</Project>

View File

@@ -8,6 +8,10 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.NUnitTestRunner.Li
EndProject EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.NUnitTestRunner.Test", "WoofWare.NUnitTestRunner\WoofWare.NUnitTestRunner.Test\WoofWare.NUnitTestRunner.Test.fsproj", "{443B01B3-2A8C-45CF-96D6-1D890EEA0533}" Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.NUnitTestRunner.Test", "WoofWare.NUnitTestRunner\WoofWare.NUnitTestRunner.Test\WoofWare.NUnitTestRunner.Test.fsproj", "{443B01B3-2A8C-45CF-96D6-1D890EEA0533}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WoofWare.NUnitTestRunner.StartupHook", "WoofWare.NUnitTestRunner.StartupHook\WoofWare.NUnitTestRunner.StartupHook.csproj", "{E2C73D96-570C-4E3C-B997-707AF8BB0E6A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WoofWare.NUnitTestRunner.StartupHookLogic", "WoofWare.NUnitTestRunner.StartupHookLogic\WoofWare.NUnitTestRunner.StartupHookLogic.csproj", "{A70627C8-9D19-42C2-AFEB-CFBDDDCE045D}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -30,5 +34,13 @@ Global
{443B01B3-2A8C-45CF-96D6-1D890EEA0533}.Debug|Any CPU.Build.0 = Debug|Any CPU {443B01B3-2A8C-45CF-96D6-1D890EEA0533}.Debug|Any CPU.Build.0 = Debug|Any CPU
{443B01B3-2A8C-45CF-96D6-1D890EEA0533}.Release|Any CPU.ActiveCfg = Release|Any CPU {443B01B3-2A8C-45CF-96D6-1D890EEA0533}.Release|Any CPU.ActiveCfg = Release|Any CPU
{443B01B3-2A8C-45CF-96D6-1D890EEA0533}.Release|Any CPU.Build.0 = Release|Any CPU {443B01B3-2A8C-45CF-96D6-1D890EEA0533}.Release|Any CPU.Build.0 = Release|Any CPU
{E2C73D96-570C-4E3C-B997-707AF8BB0E6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E2C73D96-570C-4E3C-B997-707AF8BB0E6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E2C73D96-570C-4E3C-B997-707AF8BB0E6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E2C73D96-570C-4E3C-B997-707AF8BB0E6A}.Release|Any CPU.Build.0 = Release|Any CPU
{A70627C8-9D19-42C2-AFEB-CFBDDDCE045D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A70627C8-9D19-42C2-AFEB-CFBDDDCE045D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A70627C8-9D19-42C2-AFEB-CFBDDDCE045D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A70627C8-9D19-42C2-AFEB-CFBDDDCE045D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@@ -1,402 +1,84 @@
namespace WoofWare.NUnitTestRunner namespace WoofWare.NUnitTestRunner
open System open System
open WoofWare.DotnetRuntimeLocator open System.Diagnostics
open System.IO open System.IO
open System.Reflection open System.Reflection
open System.Runtime.Loader open System.Threading.Tasks
open Spectre.Console
// Fix for https://github.com/Smaug123/unofficial-nunit-runner/issues/8
// Set AppContext.BaseDirectory to where the test DLL is.
// (This tells the DLL loader to look next to the test DLL for dependencies.)
type SetBaseDir (testDll : FileInfo) =
let oldBaseDir = AppContext.BaseDirectory
do AppContext.SetData ("APP_CONTEXT_BASE_DIRECTORY", testDll.Directory.FullName)
interface IDisposable with
member _.Dispose () =
AppContext.SetData ("APP_CONTEXT_BASE_DIRECTORY", oldBaseDir)
type Ctx (dll : FileInfo, runtimes : DirectoryInfo list) =
inherit AssemblyLoadContext ()
override this.Load (target : AssemblyName) : Assembly =
let path = Path.Combine (dll.Directory.FullName, $"%s{target.Name}.dll")
if File.Exists path then
this.LoadFromAssemblyPath path
else
runtimes
|> List.tryPick (fun di ->
let path = Path.Combine (di.FullName, $"%s{target.Name}.dll")
if File.Exists path then
this.LoadFromAssemblyPath path |> Some
else
None
)
|> Option.defaultValue null
module Program = module Program =
let selectRuntime // This is actually transcribed into C# in WoofWare.NUnitTestRunner.StartupHookLogic.
(config : RuntimeOptions) let execute argv =
(f : DotnetEnvironmentInfo)
: Choice<DotnetEnvironmentFrameworkInfo, DotnetEnvironmentSdkInfo> option
=
let rollForward =
match Environment.GetEnvironmentVariable "DOTNET_ROLL_FORWARD" with
| null ->
config.RollForward
|> Option.map RollForward.Parse
|> Option.defaultValue RollForward.Minor
| s -> RollForward.Parse s
let desiredVersions =
match config.Framework with
| Some f -> [ Version f.Version, f.Name ]
| None ->
match config.Frameworks with
| Some f -> f |> List.map (fun f -> Version f.Version, f.Name)
| None ->
failwith
"Could not deduce a framework version due to lack of either Framework or Frameworks in runtimeconfig"
let compatiblyNamedRuntimes =
f.Frameworks
|> Seq.collect (fun availableFramework ->
desiredVersions
|> List.choose (fun (desiredVersion, desiredName) ->
if desiredName = availableFramework.Name then
Some
{|
Desired = desiredVersion
Name = desiredName
Installed = availableFramework
InstalledVersion = Version availableFramework.Version
|}
else
None
)
)
|> Seq.toList
match rollForward with
| RollForward.Minor ->
let available =
compatiblyNamedRuntimes
|> Seq.filter (fun data ->
data.InstalledVersion.Major = data.Desired.Major
&& data.InstalledVersion.Minor >= data.Desired.Minor
)
|> Seq.groupBy (fun data -> data.Name)
|> Seq.map (fun (name, data) ->
let data =
data
|> Seq.minBy (fun data -> data.InstalledVersion.Minor, data.InstalledVersion.Build)
name, data.Installed
)
// TODO: how do we select between many available frameworks?
|> Seq.tryHead
match available with
| Some (_, f) -> Some (Choice1Of2 f)
| None ->
// TODO: maybe we can ask the SDK. But we keep on trucking: maybe we're self-contained,
// and we'll actually find all the runtime next to the DLL.
None
| _ -> failwith "non-minor RollForward not supported yet; please shout if you want it"
let locateRuntimes (dll : FileInfo) : DirectoryInfo list =
let runtimeConfig =
let name =
if not (dll.Name.EndsWith (".dll", StringComparison.OrdinalIgnoreCase)) then
failwith $"Expected DLL %s{dll.FullName} to end in .dll"
dll.Name.Substring (0, dll.Name.Length - 4)
Path.Combine (dll.Directory.FullName, $"%s{name}.runtimeconfig.json")
|> File.ReadAllText
|> System.Text.Json.Nodes.JsonNode.Parse
|> RuntimeConfig.jsonParse
|> fun f -> f.RuntimeOptions
let availableRuntimes = DotnetEnvironmentInfo.Get ()
let runtime = selectRuntime runtimeConfig availableRuntimes
match runtime with
| None ->
// Keep on trucking: let's be optimistic and hope that we're self-contained.
[ dll.Directory ]
| Some (Choice1Of2 runtime) -> [ dll.Directory ; DirectoryInfo $"%s{runtime.Path}/%s{runtime.Version}" ]
| Some (Choice2Of2 sdk) -> [ dll.Directory ; DirectoryInfo sdk.Path ]
let main argv =
let startTime = DateTimeOffset.Now let startTime = DateTimeOffset.Now
let testDll, filter, trxPath = let args = argv |> List.ofArray |> Args.Parse
match argv |> List.ofSeq with
| [ dll ] -> FileInfo dll, None, None
| [ dll ; "--trx" ; trxPath ] -> FileInfo dll, None, Some (FileInfo trxPath)
| [ dll ; "--filter" ; filter ] -> FileInfo dll, Some (Filter.parse filter), None
| [ dll ; "--trx" ; trxPath ; "--filter" ; filter ] ->
FileInfo dll, Some (Filter.parse filter), Some (FileInfo trxPath)
| [ dll ; "--filter" ; filter ; "--trx" ; trxPath ] ->
FileInfo dll, Some (Filter.parse filter), Some (FileInfo trxPath)
| _ ->
failwith
"provide exactly one arg, a test DLL; then optionally `--filter <filter>` and/or `--trx <output-filename>`."
let filter = let filter =
match filter with match args.Filter with
| Some filter -> Filter.shouldRun filter | Some (_, filter) -> Filter.shouldRun filter
| None -> fun _ _ -> true | None -> fun _ _ -> true
let progress = Progress.spectre () let stderr =
let consoleSettings = AnsiConsoleSettings ()
consoleSettings.Out <- AnsiConsoleOutput Console.Error
AnsiConsole.Create consoleSettings
use _ = new SetBaseDir (testDll) let progress = Progress.spectre stderr
let ctx = Ctx (testDll, locateRuntimes testDll) let runtime = DotnetRuntime.locate args.Dll
let assy = ctx.LoadFromAssemblyPath testDll.FullName
match args.Logging with
| LogLevel.Nothing -> ()
| LogLevel.Verbose ->
for d in runtime do
stderr.WriteLine $".NET runtime directory: %s{d.FullName}"
use contexts = TestContexts.Empty ()
let ctx = LoadContext (args.Dll, runtime, contexts)
let assy = ctx.LoadFromAssemblyPath args.Dll.FullName
let attrs = AssemblyLevelAttributes.get assy
let levelOfParallelism =
match args.LevelOfParallelism, attrs.Parallelism with
| None, None -> None
| Some taken, Some ignored ->
match args.Logging with
| LogLevel.Nothing -> ()
| LogLevel.Verbose ->
stderr.WriteLine
$"Taking parallelism %i{taken} from command line, ignoring value %i{ignored} from assembly"
Some taken
| Some x, None
| None, Some x -> Some x
let testFixtures = assy.ExportedTypes |> Seq.map TestFixture.parse |> Seq.toList let testFixtures = assy.ExportedTypes |> Seq.map TestFixture.parse |> Seq.toList
use par = new ParallelQueue (levelOfParallelism, attrs.Parallelizable)
let creationTime = DateTimeOffset.Now let creationTime = DateTimeOffset.Now
let results = testFixtures |> List.map (TestFixture.run progress filter)
let finishTime = DateTimeOffset.Now
let finishTimeHumanReadable = finishTime.ToString @"yyyy-MM-dd HH:mm:ss"
let nowMachine = finishTime.ToString @"yyyy-MM-dd_HH_mm_ss"
let testListId = Guid.NewGuid ()
let testDefinitions, testEntries =
results
|> List.collect (fun results -> results.IndividualTestRunMetadata)
|> List.map (fun (data, _) ->
let defn =
{
Name = data.TestName
Storage = assy.Location.ToLowerInvariant ()
Id = data.TestId
Execution =
{
Id = data.ExecutionId
}
TestMethod =
{
CodeBase = assy.Location
AdapterTypeName = Uri "executor://woofware/"
ClassName = data.ClassName
Name = data.TestName
}
}
let entry : TrxTestEntry =
{
TestListId = testListId
ExecutionId = data.ExecutionId
TestId = data.TestId
}
defn, entry
)
|> List.unzip
let hostname = Environment.MachineName
let settings =
{
Name = "default"
Id = Guid.NewGuid ()
Deployment =
{
RunDeploymentRoot = $"_%s{hostname}_%s{nowMachine}"
}
}
let testList : TrxTestListEntry =
{
Id = testListId
Name = "All"
}
let counters =
(TrxCounters.Zero, results)
// TODO: this is woefully inefficient
||> List.fold (fun counters results ->
let counters =
(counters, results.Failed)
||> List.fold (fun counters (_, _) ->
// TODO: the counters can be more specific about the failure mode
counters.AddFailed ()
)
let counters =
(counters, results.OtherFailures)
||> List.fold (fun counters _ ->
// TODO: the counters can be more specific about the failure mode
counters.AddFailed ()
)
(counters, results.Success)
||> List.fold (fun counters (_, success, _) ->
match success with
| TestMemberSuccess.Ok -> counters.AddPassed ()
| TestMemberSuccess.Ignored _
| TestMemberSuccess.Explicit _ -> counters.AddNotExecuted ()
| TestMemberSuccess.Inconclusive _ -> counters.AddInconclusive ()
)
)
// TODO: I'm sure we can do better than this; there's a whole range of possible
// states!
let outcome =
if counters.Failed > 0u then
TrxOutcome.Failed
else
TrxOutcome.Completed
let resultSummary : TrxResultsSummary =
{
Outcome = outcome
Counters = counters
Output =
{
StdOut = None
ErrorInfo = None
}
RunInfos =
[
// TODO: capture stdout
]
}
let times : TrxReportTimes =
{
Creation = creationTime
Queuing = startTime
Start = startTime
Finish = finishTime
}
let magicGuid = Guid.Parse "13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b"
let results = let results =
results testFixtures
|> List.collect (fun results -> results.IndividualTestRunMetadata) |> List.map (TestFixture.run contexts par progress filter)
|> List.map (fun (i, cause) -> |> Task.WhenAll
let exc =
match cause with
| Choice2Of3 _ -> None
| Choice1Of3 (TestMemberFailure.Malformed reasons) ->
{
StackTrace = None
Message = reasons |> String.concat "\n" |> Some
}
|> Some
| Choice1Of3 (TestMemberFailure.Failed fail)
| Choice1Of3 (TestMemberFailure.Failed fail)
| Choice1Of3 (TestMemberFailure.Failed fail) ->
((None, None), fail)
||> List.fold (fun (stackTrace, message) tf ->
match tf with
| TestFailure.TestFailed (UserMethodFailure.Threw (_, exc))
| TestFailure.SetUpFailed (UserMethodFailure.Threw (_, exc))
| TestFailure.TearDownFailed (UserMethodFailure.Threw (_, exc)) ->
let stackTrace =
match stackTrace with
| None -> (exc : Exception).ToString ()
| Some s -> s
(Some stackTrace, message) let timeout =
| TestFailure.TestFailed (UserMethodFailure.ReturnedNonUnit (_, ret)) match args.Timeout with
| TestFailure.SetUpFailed (UserMethodFailure.ReturnedNonUnit (_, ret)) | None -> TimeSpan.FromHours 2.0
| TestFailure.TearDownFailed (UserMethodFailure.ReturnedNonUnit (_, ret)) -> | Some t -> t
let newMessage = $"returned non-unit value %O{ret}"
let message = if not (results.Wait timeout) then
match message with failwith "Tests failed to terminate within two hours"
| None -> newMessage
| Some message -> $"%s{message}\n%s{newMessage}"
(stackTrace, Some message) let results = results.Result |> Seq.concat |> List.ofSeq
)
|> fun (stackTrace, message) ->
{
StackTrace = stackTrace
Message = message
}
|> Some
| Choice3Of3 (UserMethodFailure.Threw (_, exc)) ->
{
StackTrace = (exc : Exception).ToString () |> Some
Message = None
}
|> Some
| Choice3Of3 (UserMethodFailure.ReturnedNonUnit (_, ret)) ->
{
Message = $"returned non-unit value %O{ret}" |> Some
StackTrace = None
}
|> Some
let outcome = let report = BuildTrxReport.build assy creationTime startTime results
match cause with
| Choice1Of3 _ -> TrxTestOutcome.Failed
| Choice2Of3 TestMemberSuccess.Ok -> TrxTestOutcome.Passed
| Choice2Of3 (TestMemberSuccess.Inconclusive _) -> TrxTestOutcome.Inconclusive
| Choice2Of3 (TestMemberSuccess.Ignored _)
| Choice2Of3 (TestMemberSuccess.Explicit _) -> TrxTestOutcome.NotExecuted
// TODO: we can totally do better here, more fine-grained classification
| Choice3Of3 _ -> TrxTestOutcome.Failed
{ match args.Trx with
ExecutionId = i.ExecutionId
TestId = i.TestId
TestName = i.TestName
ComputerName = i.ComputerName
Duration = i.End - i.Start
StartTime = i.Start
EndTime = i.End
TestType = magicGuid
Outcome = outcome
TestListId = testListId
RelativeResultsDirectory = i.ExecutionId.ToString () // for some reason
Output =
match i.StdOut, i.StdErr, exc with
| None, None, None -> None
// TODO surely stderr can be emitted
| stdout, _stderr, exc ->
Some
{
TrxOutput.StdOut = stdout
ErrorInfo = exc
}
}
)
let report : TrxReport =
{
Id = Guid.NewGuid ()
Name = $"@%s{hostname} %s{finishTimeHumanReadable}"
Times = times
Settings = settings
Results = results
TestDefinitions = testDefinitions
TestEntries = testEntries
TestLists = [ testList ]
ResultsSummary = resultSummary
}
match trxPath with
| Some trxPath -> | Some trxPath ->
let contents = TrxReport.toXml report |> fun d -> d.OuterXml let contents = TrxReport.toXml report |> fun d -> d.OuterXml
trxPath.Directory.Create () trxPath.Directory.Create ()
@@ -404,10 +86,56 @@ module Program =
Console.Error.WriteLine $"Written TRX file: %s{trxPath.FullName}" Console.Error.WriteLine $"Written TRX file: %s{trxPath.FullName}"
| None -> () | None -> ()
match outcome with match report.ResultsSummary.Outcome with
| TrxOutcome.Completed -> 0 | TrxOutcome.Completed -> 0
| _ -> 1 | _ -> 1
let main argv =
let args = argv |> List.ofArray |> Args.Parse
let psi = ProcessStartInfo "dotnet"
match args.Trx with
| None -> ()
| Some trx -> psi.EnvironmentVariables.Add ("WOOFWARE_NUNIT_GENERATE_TRX", trx.FullName)
match args.LevelOfParallelism with
| None -> ()
| Some par -> psi.EnvironmentVariables.Add ("WOOFWARE_NUNIT_PARALLELISM_LEVEL", string<int> par)
match args.Filter with
| None -> ()
| Some (filter, _) -> psi.EnvironmentVariables.Add ("WOOFWARE_NUNIT_FILTER", filter)
match args.Timeout with
| None -> ()
| Some timeout ->
psi.EnvironmentVariables.Add ("WOOFWARE_NUNIT_TIMEOUT_SECS", string<int> (int<float> timeout.TotalSeconds))
psi.ArgumentList.Add "exec"
psi.ArgumentList.Add args.Dll.FullName
let us = Assembly.GetExecutingAssembly().Location |> FileInfo
let startupHook =
Path.Combine (us.Directory.FullName, "WoofWare.NUnitTestRunner.StartupHook.dll")
psi.EnvironmentVariables.Add ("DOTNET_STARTUP_HOOKS", startupHook)
psi.EnvironmentVariables.Add (
"WOOFWARE_NUNIT_LIB",
Path.Combine (us.Directory.FullName, "WoofWare.NUnitTestRunner.Lib.dll")
)
use proc = new Process ()
proc.StartInfo <- psi
if not (proc.Start ()) then
failwith "Failed to start dotnet"
proc.WaitForExit ()
proc.ExitCode
[<EntryPoint>] [<EntryPoint>]
let reallyMain argv = let reallyMain argv =
// Hack to make sure `finally`s get run. // Hack to make sure `finally`s get run.

View File

@@ -4,21 +4,21 @@ open Spectre.Console
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module Progress = module Progress =
let spectre () : ITestProgress = let spectre (console : IAnsiConsole) : ITestProgress =
{ new ITestProgress with { new ITestProgress with
member _.OnTestFailed name failure = member _.OnTestFailed name failure =
AnsiConsole.Console.MarkupLine console.MarkupLine
$"[red]Test '%s{Markup.Escape name}' failed: %s{Markup.Escape (failure.ToString ())}[/]" $"[red]Test '%s{Markup.Escape name}' failed: %s{Markup.Escape (failure.ToString ())}[/]"
member _.OnTestFixtureStart name testCount = member _.OnTestFixtureStart name testCount =
AnsiConsole.Console.MarkupLine $"[white]Running tests: %s{Markup.Escape name}[/]" console.MarkupLine $"[white]Running tests: %s{Markup.Escape name}[/]"
member _.OnTestMemberFinished name = member _.OnTestMemberFinished name =
AnsiConsole.Console.MarkupLine $"[gray]Finished test: %s{Markup.Escape name}[/]" console.MarkupLine $"[gray]Finished test: %s{Markup.Escape name}[/]"
member _.OnTestMemberSkipped name = member _.OnTestMemberSkipped name =
AnsiConsole.Console.MarkupLine $"[yellow]Skipping test due to filter: %s{Markup.Escape name}[/]" console.MarkupLine $"[yellow]Skipping test due to filter: %s{Markup.Escape name}[/]"
member _.OnTestMemberStart name = member _.OnTestMemberStart name =
AnsiConsole.Console.MarkupLine $"[white]Running test: %s{Markup.Escape name}[/]" console.MarkupLine $"[white]Running test: %s{Markup.Escape name}[/]"
} }

View File

@@ -1,22 +0,0 @@
namespace WoofWare.NUnitTestRunner
[<RequireQualifiedAccess>]
module internal Seq =
let tryMinBy (f : 'a -> 'b) (s : 'a seq) : 'a option =
use enum = s.GetEnumerator ()
if not (enum.MoveNext ()) then
None
else
let mutable answer = enum.Current
let mutable fAnswer = f enum.Current
while enum.MoveNext () do
let fNext = f enum.Current
if fNext < fAnswer then
answer <- enum.Current
Some answer

View File

@@ -11,6 +11,8 @@ module TestList =
[<Test>] [<Test>]
let ``combinations has right size`` () = let ``combinations has right size`` () =
let property (xs : int list list) = let property (xs : int list list) =
let xs = if xs.Length > 6 then xs |> List.take 6 else xs
let xs = xs |> List.map (fun xs -> if xs.Length > 6 then xs |> List.take 6 else xs)
let combs = List.combinations xs let combs = List.combinations xs
combs.Length combs.Length

View File

@@ -197,6 +197,7 @@ Running all tests in /Users/patrick/Documents/GitHub/TestRunner/TestRunner/TestR
Ensure version is monotonic: Not yet published Ensure version is monotonic: Not yet published
NUnit Adapter 4.5.0.0: Test execution complete NUnit Adapter 4.5.0.0: Test execution complete
""" """
StdErr = None
ErrorInfo = None ErrorInfo = None
} }
Outcome = TrxOutcome.Failed Outcome = TrxOutcome.Failed

View File

@@ -16,7 +16,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ApiSurface" Version="4.0.41" /> <PackageReference Include="ApiSurface" Version="4.0.43" />
<PackageReference Include="FsCheck" Version="3.0.0-rc3" /> <PackageReference Include="FsCheck" Version="3.0.0-rc3" />
<PackageReference Include="FsUnit" Version="6.0.0" /> <PackageReference Include="FsUnit" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />

View File

@@ -2,7 +2,8 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<RollForward>Major</RollForward>
<PackAsTool>true</PackAsTool> <PackAsTool>true</PackAsTool>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<Authors>Patrick Stevens</Authors> <Authors>Patrick Stevens</Authors>
@@ -16,15 +17,9 @@
<PackageId>WoofWare.NUnitTestRunner</PackageId> <PackageId>WoofWare.NUnitTestRunner</PackageId>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarnOn>FS3559</WarnOn> <WarnOn>FS3559</WarnOn>
<WoofWareMyriadPluginVersion>2.1.42</WoofWareMyriadPluginVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="RuntimeConfig.fs" />
<Compile Include="GeneratedRuntimeConfig.fs">
<MyriadFile>RuntimeConfig.fs</MyriadFile>
</Compile>
<Compile Include="Seq.fs" />
<Compile Include="Progress.fs" /> <Compile Include="Progress.fs" />
<Compile Include="Program.fs" /> <Compile Include="Program.fs" />
<None Include="..\README.md"> <None Include="..\README.md">
@@ -35,18 +30,12 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\WoofWare.NUnitTestRunner.Lib\WoofWare.NUnitTestRunner.Lib.fsproj" /> <ProjectReference Include="..\WoofWare.NUnitTestRunner.Lib\WoofWare.NUnitTestRunner.Lib.fsproj" />
<ProjectReference Include="..\WoofWare.NUnitTestRunner.StartupHookLogic\WoofWare.NUnitTestRunner.StartupHookLogic.csproj" />
<ProjectReference Include="..\WoofWare.NUnitTestRunner.StartupHook\WoofWare.NUnitTestRunner.StartupHook.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Spectre.Console" Version="0.49.1" /> <PackageReference Include="Spectre.Console" Version="0.49.1" />
<PackageReference Include="WoofWare.DotnetRuntimeLocator" Version="0.1.4" />
<PackageReference Include="WoofWare.Myriad.Plugins.Attributes" Version="3.1.6" />
<PackageReference Include="Myriad.SDK" Version="0.8.3" />
<PackageReference Include="WoofWare.Myriad.Plugins" Version="$(WoofWareMyriadPluginVersion)" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<MyriadSdkGenerator Include="$(NuGetPackageRoot)/woofware.myriad.plugins/$(WoofWareMyriadPluginVersion)/lib/net6.0/WoofWare.Myriad.Plugins.dll" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -5,6 +5,7 @@
], ],
"pathFilters": [ "pathFilters": [
"./", "./",
"^./WoofWare.NUnitTestRunner.Test",
":/WoofWare.NUnitTestRunner.Lib", ":/WoofWare.NUnitTestRunner.Lib",
":/Directory.Build.props", ":/Directory.Build.props",
":/README.md" ":/README.md"

6
flake.lock generated
View File

@@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1717399147, "lastModified": 1720781449,
"narHash": "sha256-eCWaE/q1VItpFAxxLVt171MdtDcjEnwi6QB/yuF73JU=", "narHash": "sha256-po3TZO9kcZwzvkyMJKb0WCzzDtiHWD34XeRaX1lWXp0=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "4a4ecb0ab415c9fccfb005567a215e6a9564cdf5", "rev": "8b5a3d5a1d951344d683b442c0739010b80039db",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -17,7 +17,7 @@
dotnet-sdk = pkgs.dotnet-sdk_8; dotnet-sdk = pkgs.dotnet-sdk_8;
dotnet-runtime = pkgs.dotnetCorePackages.runtime_8_0; dotnet-runtime = pkgs.dotnetCorePackages.runtime_8_0;
version = "0.1"; version = "0.1";
dotnetTool = dllOverride: toolName: toolVersion: sha256: dotnetTool = dllOverride: toolName: toolVersion: hash:
pkgs.stdenvNoCC.mkDerivation rec { pkgs.stdenvNoCC.mkDerivation rec {
name = toolName; name = toolName;
version = toolVersion; version = toolVersion;
@@ -25,7 +25,7 @@
src = pkgs.fetchNuGet { src = pkgs.fetchNuGet {
pname = name; pname = name;
version = version; version = version;
sha256 = sha256; hash = hash;
installPhase = ''mkdir -p $out/bin && cp -r tools/net6.0/any/* $out/bin''; installPhase = ''mkdir -p $out/bin && cp -r tools/net6.0/any/* $out/bin'';
}; };
installPhase = let installPhase = let
@@ -43,8 +43,8 @@
}; };
in { in {
packages = { packages = {
fantomas = dotnetTool null "fantomas" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fantomas.version (builtins.head (builtins.filter (elem: elem.pname == "fantomas") ((import ./nix/deps.nix) {fetchNuGet = x: x;}))).sha256; fantomas = dotnetTool null "fantomas" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fantomas.version (builtins.head (builtins.filter (elem: elem.pname == "fantomas") ((import ./nix/deps.nix) {fetchNuGet = x: x;}))).hash;
fsharp-analyzers = dotnetTool "FSharp.Analyzers.Cli" "fsharp-analyzers" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fsharp-analyzers.version (builtins.head (builtins.filter (elem: elem.pname == "fsharp-analyzers") ((import ./nix/deps.nix) {fetchNuGet = x: x;}))).sha256; fsharp-analyzers = dotnetTool "FSharp.Analyzers.Cli" "fsharp-analyzers" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fsharp-analyzers.version (builtins.head (builtins.filter (elem: elem.pname == "fsharp-analyzers") ((import ./nix/deps.nix) {fetchNuGet = x: x;}))).hash;
default = pkgs.buildDotnetModule { default = pkgs.buildDotnetModule {
inherit pname version dotnet-sdk dotnet-runtime; inherit pname version dotnet-sdk dotnet-runtime;
name = "unofficial-nunit-runner"; name = "unofficial-nunit-runner";
@@ -56,13 +56,20 @@
doCheck = true; doCheck = true;
}; };
}; };
devShell = pkgs.mkShell { devShells = {
buildInputs = [dotnet-sdk]; default = pkgs.mkShell {
packages = [ packages = [
pkgs.alejandra dotnet-sdk
pkgs.nodePackages.markdown-link-check pkgs.alejandra
pkgs.shellcheck pkgs.nodePackages.markdown-link-check
]; pkgs.shellcheck
];
};
net6 = pkgs.mkShell {
packages = [
pkgs.dotnetCorePackages.runtime_6_0
];
};
}; };
}); });
} }

View File

@@ -3,202 +3,267 @@
{fetchNuGet}: [ {fetchNuGet}: [
(fetchNuGet { (fetchNuGet {
pname = "ApiSurface"; pname = "ApiSurface";
version = "4.0.41"; version = "4.0.43";
sha256 = "03kfa5ngmgkik9lc58sp8s9rrh9g40hhgjnrv662ks0d0y2i9i89"; hash = "sha256-CO5a0ZCWvD4fZXQL9l0At0y0vqmN3TT2+TuUw4ZNoC8=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "fantomas"; pname = "fantomas";
version = "6.3.9"; version = "6.3.10";
sha256 = "1b34iiiff02bbzjv03zyna8xmrgs6y87zdvp5i5k58fcqpjw44sx"; hash = "sha256-2m4YevDp9CRm/Ci2hguDXd6DUMElRg3hNAne9LHntWM=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "FsCheck"; pname = "FsCheck";
version = "3.0.0-rc3"; version = "3.0.0-rc3";
sha256 = "1rn4x9qh479927viwww3dy0mikcdcq3pfqv1hzbbawnwxfzm17z1"; hash = "sha256-4Z9Qv+vccrXWh2Fjdwdmjc1YgW+Dcx73ESkdAnHqxOY=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "fsharp-analyzers"; pname = "fsharp-analyzers";
version = "0.26.0"; version = "0.26.0";
sha256 = "0xgv5kvbwfdvcp6s8x7xagbbi4s3mqa4ixni6pazqvyflbgnah7b"; hash = "sha256-60Bl36LOb/zVNdH2SBSuQ5O41lP9dKTNZbs5vvYs+3U=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "FSharp.Core"; pname = "FSharp.Core";
version = "6.0.0"; version = "6.0.0";
sha256 = "1hjhvr39c1vpgrdmf8xln5q86424fqkvy9nirkr29vl2461d2039"; hash = "sha256-aQDRgiGC7iTyzNEmvyd2RBCDcLG0I1dbfncHlkbeUMI=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "FSharp.Core"; pname = "FSharp.Core";
version = "8.0.300"; version = "8.0.300";
sha256 = "158xxr9hnhz2ibyzzp2d249angvxfc58ifflm4g3hz8qx9zxaq04"; hash = "sha256-BGDVf+oYfTgeqdS5iApzfT+rEhFN3P/9iuJDC1PuHZU=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "FsUnit"; pname = "FsUnit";
version = "6.0.0"; version = "6.0.0";
sha256 = "18q3p0z155znwj1l0qq3vq9nh9wl2i4mlfx4pmrnia4czr0xdkmb"; hash = "sha256-q87WQf6MqGhzvaQ7WkkUlCdoE94DY0CD5PaXEj64A6M=";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Ref";
version = "6.0.31";
hash = "sha256-rVxnpwa8B1weEzkJIxm+todHXrSdIT3KY38D09MncUI=";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.linux-arm64";
version = "6.0.31";
hash = "sha256-z6HNgiyLyXN5+QlktIZmOQh9D4KRHWd6yonmKChEji4=";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.linux-x64";
version = "6.0.31";
hash = "sha256-Rq97wUeAD2bZhe0XRLB9Ffq1MBU/u4Yq45KwRLz7DRQ=";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.osx-arm64";
version = "6.0.31";
hash = "sha256-QlAteE0egI+YzouNm4d8+yXRs9E0jfBTVl7KbMxSmnA=";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.osx-x64";
version = "6.0.31";
hash = "sha256-c1hTOPeoH7STKevPryBv4+IL/7YMfTFm5EakMvrhfI4=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.CodeCoverage"; pname = "Microsoft.CodeCoverage";
version = "17.10.0"; version = "17.10.0";
sha256 = "0s0v7jmrq85n356xv7zixvwa4z94fszjcr5vll8x4im1a2lp00f9"; hash = "sha256-yQFwqVChRtIRpbtkJr92JH2i+O7xn91NGbYgnKs8G2g=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NET.Test.Sdk"; pname = "Microsoft.NET.Test.Sdk";
version = "17.10.0"; version = "17.10.0";
sha256 = "13g8fwl09li8fc71nk13dgkb7gahd4qhamyg2xby7am63nlchhdf"; hash = "sha256-rkHIqB2mquNXF89XBTFpUL2z5msjTBsOcyjSBCh36I0=";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Host.linux-arm64";
version = "6.0.31";
hash = "sha256-mjS1IKBwxDFT2UmcyC5ZMIQ3mWSADw887CIV0pZhQRc=";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Host.linux-x64";
version = "6.0.31";
hash = "sha256-VifKEirDStL5vpjnEe0hGsODssK20XBX/Mm6j8G4QIM=";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Host.osx-arm64";
version = "6.0.31";
hash = "sha256-unElfKN3Ro852wsIzgTIXJJPoYpvVjrsSIwxKtwLUGk=";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Host.osx-x64";
version = "6.0.31";
hash = "sha256-JiIXW9FPqjEb5cZHZ0otmmbY4gpgRdTZCxxuy12AJkw=";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Ref";
version = "6.0.31";
hash = "sha256-p/KpNGwJfvoWNRn057o4t8u8La2LvsmOjF0i2W1URKU=";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.linux-arm64";
version = "6.0.31";
hash = "sha256-N3xRbF5nQo9Okixqg1mN86L7VB72bpDR0C6wlfzntPI=";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.linux-x64";
version = "6.0.31";
hash = "sha256-TE1DmzoBKO9qKoATBWZPRKXBRoRzs6AafKuubeSwgl8=";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.osx-arm64";
version = "6.0.31";
hash = "sha256-Gh+/HbETPJ2LNX9vRo+e8CmbL3YacBUXGHNVcIEQrIQ=";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.osx-x64";
version = "6.0.31";
hash = "sha256-ZZYzARixjQ42taRJLiTq0ykG1Hm8F/4RkZXM9lowhbI=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.NETCore.Platforms"; pname = "Microsoft.NETCore.Platforms";
version = "2.0.0"; version = "2.0.0";
sha256 = "1fk2fk2639i7nzy58m9dvpdnzql4vb8yl8vr19r2fp8lmj9w2jr0"; hash = "sha256-IEvBk6wUXSdyCnkj6tHahOJv290tVVT8tyemYcR0Yro=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.TestPlatform.ObjectModel"; pname = "Microsoft.TestPlatform.ObjectModel";
version = "17.10.0"; version = "17.10.0";
sha256 = "07j69cw8r39533w4p39mnj00kahazz38760in3jfc45kmlcdb26x"; hash = "sha256-3YjVGK2zEObksBGYg8b/CqoJgLQ1jUv4GCWNjDhLRh4=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Microsoft.TestPlatform.TestHost"; pname = "Microsoft.TestPlatform.TestHost";
version = "17.10.0"; version = "17.10.0";
sha256 = "1bl471s7fx9jycr0cc8rylwf34mrvlg9qn1an6l86nisavfcyb7v"; hash = "sha256-+yzP3FY6WoOosSpYnB7duZLhOPUZMQYy8zJ1d3Q4hK4=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Myriad.Sdk"; pname = "Myriad.Sdk";
version = "0.8.3"; version = "0.8.3";
sha256 = "0qv78c5s5m04xb8h17nnn2ig26zcyya91k2dpj745cm1cbnzvvgc"; hash = "sha256-7O397WKhskKOvE3MkJT37BvxorDWngDR6gTUogtDZ2M=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Nerdbank.GitVersioning"; pname = "Nerdbank.GitVersioning";
version = "3.6.139"; version = "3.6.139";
sha256 = "0npcryhq3r0c2zi940jk39h13mzc4hyg7z8gm6jdmxi1aqv1vh8c"; hash = "sha256-DMEdNlYh9tqkqQ/98zwk7NcRYBpTApLiFwzkgaHP7Fo=";
})
(fetchNuGet {
pname = "NETStandard.Library.Ref";
version = "2.1.0";
sha256 = "12n76gymxq715lkrw841vi5r84kx746cxxssp22pd08as75jzsj6";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Newtonsoft.Json"; pname = "Newtonsoft.Json";
version = "13.0.1"; version = "13.0.1";
sha256 = "0fijg0w6iwap8gvzyjnndds0q4b8anwxxvik7y8vgq97dram4srb"; hash = "sha256-K2tSVW4n4beRPzPu3rlVaBEMdGvWSv/3Q1fxaDh4Mjo=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Newtonsoft.Json"; pname = "Newtonsoft.Json";
version = "13.0.3"; version = "13.0.3";
sha256 = "0xrwysmrn4midrjal8g2hr1bbg38iyisl0svamb11arqws4w2bw7"; hash = "sha256-hy/BieY4qxBWVVsDqqOPaLy1QobiIapkbrESm6v2PHc=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "NuGet.Common"; pname = "NuGet.Common";
version = "6.10.0"; version = "6.10.1";
sha256 = "0nizrnilmlcqbm945293h8q3wfqfchb4xi8g50x4kjn0rbpd1kbh"; hash = "sha256-2gZe1zwSaZsr0nipaMBJixLEVOvR7vp75kwecSSYyfw=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "NuGet.Configuration"; pname = "NuGet.Configuration";
version = "6.10.0"; version = "6.10.1";
sha256 = "1aqaknaawnqx4mnvx9qw73wvj48jjzv0d78dzwl7m9zjlrl9myhz"; hash = "sha256-RmjvlbtJssxuWEiOcVJLtUVT0nPn7IUPb878NmJbwmM=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "NuGet.Frameworks"; pname = "NuGet.Frameworks";
version = "6.10.0"; version = "6.10.1";
sha256 = "0hrd8y31zx9a0wps49czw0qgbrakb49zn3abfgylc9xrq990zkqk"; hash = "sha256-AdfpuVDDy9zYAGgcMZoTf/fkFCJJVrxRFhsv6AI4Dd0=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "NuGet.Packaging"; pname = "NuGet.Packaging";
version = "6.10.0"; version = "6.10.1";
sha256 = "18s53cvrf51lihmaqqdf48p2qi6ky1l48jv0hvbp76cxwdg7rba4"; hash = "sha256-ogsVjOao+LKUOMhGir1flDuMPjOeR2OpkNGHtr/riH4=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "NuGet.Protocol"; pname = "NuGet.Protocol";
version = "6.10.0"; version = "6.10.1";
sha256 = "0hmv4q0ks9i34mfgpb13l01la9v3jjllfh1qd3aqv105xrqrdxac"; hash = "sha256-UeS/10z1EqswbeB0c7QgBIVOp0VGlN5ZDQqTY2/AhX8=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "NuGet.Versioning"; pname = "NuGet.Versioning";
version = "6.10.0"; version = "6.10.1";
sha256 = "1x19njx4x0sw9fz8y5fibi15xfsrw5avir0cx0599yd7p3ykik5g"; hash = "sha256-jOh27AORk0TIhVePDVAgVhh6FuUo2v3oh/Xapcw7UVI=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "NUnit"; pname = "NUnit";
version = "4.1.0"; version = "4.1.0";
sha256 = "0fj6xwgqaxq3mrai86bklclfmjkzf038mrslwfqf4ignaz9f7g5j"; hash = "sha256-srzj0lf2ReKw41TnigZwf8rqKKNzGRRVrgN3hR/vRjo=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "NUnit3TestAdapter"; pname = "NUnit3TestAdapter";
version = "4.5.0"; version = "4.5.0";
sha256 = "1srx1629s0k1kmf02nmz251q07vj6pv58mdafcr5dr0bbn1fh78i"; hash = "sha256-ER3ogl0L5FYyc6pVVPY1ch+AQxG/WgFcnWECnYQJPes=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "Spectre.Console"; pname = "Spectre.Console";
version = "0.49.1"; version = "0.49.1";
sha256 = "0fhl96p3xjd5k1wwvhs80cp35rrlgnza6mw9vy0knhmf7ji9b95n"; hash = "sha256-tqSVojyuQjuB34lXo759NOcyLgNIw815mKXJPq5JFDo=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "System.Formats.Asn1"; pname = "System.Formats.Asn1";
version = "6.0.0"; version = "6.0.0";
sha256 = "1vvr7hs4qzjqb37r0w1mxq7xql2b17la63jwvmgv65s1hj00g8r9"; hash = "sha256-KaMHgIRBF7Nf3VwOo+gJS1DcD+41cJDPWFh+TDQ8ee8=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "System.IO.Abstractions"; pname = "System.IO.Abstractions";
version = "4.2.13"; version = "4.2.13";
sha256 = "0s784iphsmj4vhkrzq9q3w39vsn76w44zclx3hsygsw458zbyh4y"; hash = "sha256-nkC/PiqE6+c1HJ2yTwg3x+qdBh844Z8n3ERWDW8k6Gg=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "System.IO.FileSystem.AccessControl"; pname = "System.IO.FileSystem.AccessControl";
version = "4.5.0"; version = "4.5.0";
sha256 = "1gq4s8w7ds1sp8f9wqzf8nrzal40q5cd2w4pkf4fscrl2ih3hkkj"; hash = "sha256-ck44YBQ0M+2Im5dw0VjBgFD1s0XuY54cujrodjjSBL8=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "System.Reflection.Metadata"; pname = "System.Reflection.Metadata";
version = "1.6.0"; version = "1.6.0";
sha256 = "1wdbavrrkajy7qbdblpbpbalbdl48q3h34cchz24gvdgyrlf15r4"; hash = "sha256-JJfgaPav7UfEh4yRAQdGhLZF1brr0tUWPl6qmfNWq/E=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "System.Security.AccessControl"; pname = "System.Security.AccessControl";
version = "4.5.0"; version = "4.5.0";
sha256 = "1wvwanz33fzzbnd2jalar0p0z3x0ba53vzx1kazlskp7pwyhlnq0"; hash = "sha256-AFsKPb/nTk2/mqH/PYpaoI8PLsiKKimaXf+7Mb5VfPM=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "System.Security.Cryptography.Pkcs"; pname = "System.Security.Cryptography.Pkcs";
version = "6.0.4"; version = "6.0.4";
sha256 = "0hh5h38pnxmlrnvs72f2hzzpz4b2caiiv6xf8y7fzdg84r3imvfr"; hash = "sha256-2e0aRybote+OR66bHaNiYpF//4fCiaO3zbR2e9GABUI=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "System.Security.Cryptography.ProtectedData"; pname = "System.Security.Cryptography.ProtectedData";
version = "4.4.0"; version = "4.4.0";
sha256 = "1q8ljvqhasyynp94a1d7jknk946m20lkwy2c3wa8zw2pc517fbj6"; hash = "sha256-Ri53QmFX8I8UH0x4PikQ1ZA07ZSnBUXStd5rBfGWFOE=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "System.Security.Principal.Windows"; pname = "System.Security.Principal.Windows";
version = "4.5.0"; version = "4.5.0";
sha256 = "0rmj89wsl5yzwh0kqjgx45vzf694v9p92r4x4q6yxldk1cv1hi86"; hash = "sha256-BkUYNguz0e4NJp1kkW7aJBn3dyH9STwB5N8XqnlCsmY=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "System.Text.Encodings.Web"; pname = "System.Text.Encodings.Web";
version = "7.0.0"; version = "7.0.0";
sha256 = "1151hbyrcf8kyg1jz8k9awpbic98lwz9x129rg7zk1wrs6vjlpxl"; hash = "sha256-tF8qt9GZh/nPy0mEnj6nKLG4Lldpoi/D8xM5lv2CoYQ=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "System.Text.Json"; pname = "System.Text.Json";
version = "7.0.3"; version = "7.0.3";
sha256 = "0zjrnc9lshagm6kdb9bdh45dmlnkpwcpyssa896sda93ngbmj8k9"; hash = "sha256-aSJZ17MjqaZNQkprfxm/09LaCoFtpdWmqU9BTROzWX4=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "WoofWare.DotnetRuntimeLocator"; pname = "WoofWare.DotnetRuntimeLocator";
version = "0.1.4"; version = "0.1.9";
sha256 = "19pp4qlyf18g704ppbcsm1rhjqjpi84py18yljj9nx70331m8bpg"; hash = "sha256-0v8JQgGjS3tseA28OFmYZUcinYRArjs28BwVy3oczJM=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "WoofWare.Myriad.Plugins"; pname = "WoofWare.Myriad.Plugins";
version = "2.1.42"; version = "2.1.52";
sha256 = "0px46m734gsn1xa97111v1nwkyc2j52bw7z4bjdljzkmzzmnqa91"; hash = "sha256-1uOnjD4YxdM0SlNC+U85ffSOoJtEz9yo9ILBn5gJnQ0=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "WoofWare.Myriad.Plugins.Attributes"; pname = "WoofWare.Myriad.Plugins.Attributes";
version = "3.1.6"; version = "3.1.9";
sha256 = "0786pr1p0nq0854mqi2cddmh185j3jihwn6azz9wiy6nxawjbrd2"; hash = "sha256-atV9vJlhLvXYrJvS9ET1A7M+LY9IdgBSTyGhz2Vom5U=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "WoofWare.PrattParser"; pname = "WoofWare.PrattParser";
version = "0.1.2"; version = "0.2.2";
sha256 = "0spypcwsbn805yrs6grjj68ccva902lhkq93mxy32rdply1xs34q"; hash = "sha256-OCsHlp/HYB/i1+h0ixq+0zxO1bXFQ6kpEWIONkOr+TE=";
}) })
] ]