├── src
├── icon.png
├── kzu.snk
├── AssemblyFixture
│ ├── build
│ │ └── xunit.assemblyfixture.targets
│ ├── TestFramework.cs
│ ├── IAssemblyFixture.cs
│ ├── TestFrameworkExecutor.cs
│ ├── AssemblyFixture.csproj
│ ├── TestAssemblyRunner.cs
│ └── TestCollectionRunner.cs
├── Tests
│ ├── AssemblyFixture.Tests.csproj
│ └── Samples.cs
├── nuget.config
├── Directory.Build.props
└── Directory.Build.targets
├── _config.yml
├── assets
├── img
│ └── xunit-purple.png
└── css
│ └── style.scss
├── Directory.Build.rsp
├── .github
├── dependabot.yml
├── workflows
│ ├── changelog.config
│ ├── dotnet-file.yml
│ ├── sponsor.yml
│ ├── publish.yml
│ ├── includes.yml
│ ├── changelog.yml
│ ├── test
│ │ └── action.yml
│ ├── build.yml
│ └── combine-prs.yml
└── release.yml
├── .gitignore
├── .gitattributes
├── license.txt
├── changelog.md
├── AssemblyFixture.sln
├── .editorconfig
├── readme.md
└── .netconfig
/src/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devlooped/xunit.assemblyfixture/HEAD/src/icon.png
--------------------------------------------------------------------------------
/src/kzu.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devlooped/xunit.assemblyfixture/HEAD/src/kzu.snk
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-slate
2 |
3 | exclude: [ 'src/', '*.sln', 'Gemfile*', '*.rsp' ]
--------------------------------------------------------------------------------
/assets/img/xunit-purple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devlooped/xunit.assemblyfixture/HEAD/assets/img/xunit-purple.png
--------------------------------------------------------------------------------
/Directory.Build.rsp:
--------------------------------------------------------------------------------
1 | # See https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-response-files
2 | -nr:false
3 | -m:1
4 | -v:m
5 | -clp:Summary;ForceNoAlign
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Please see the documentation for all configuration options:
2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
3 |
4 | version: 2
5 | updates:
6 | - package-ecosystem: nuget
7 | directory: /
8 | schedule:
9 | interval: daily
10 |
--------------------------------------------------------------------------------
/.github/workflows/changelog.config:
--------------------------------------------------------------------------------
1 | usernames-as-github-logins=true
2 | issues_wo_labels=true
3 | pr_wo_labels=true
4 | exclude-labels=bydesign,dependencies,duplicate,question,invalid,wontfix,need info,docs
5 | enhancement-label=:sparkles: Implemented enhancements:
6 | bugs-label=:bug: Fixed bugs:
7 | issues-label=:hammer: Other:
8 | pr-label=:twisted_rightwards_arrows: Merged:
9 | unreleased=false
10 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet-file.yml:
--------------------------------------------------------------------------------
1 | # Synchronizes .netconfig-configured files with dotnet-file
2 | name: dotnet-file
3 | on:
4 | workflow_dispatch:
5 | schedule:
6 | - cron: "0 0 * * *"
7 | push:
8 | branches: [ 'dotnet-file' ]
9 |
10 | env:
11 | DOTNET_NOLOGO: true
12 |
13 | jobs:
14 | run:
15 | uses: devlooped/oss/.github/workflows/dotnet-file-core.yml@main
16 | secrets: inherit
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | app
3 | obj
4 | artifacts
5 | pack
6 | TestResults
7 | .vs
8 | .vscode
9 | .idea
10 |
11 | *.suo
12 | *.sdf
13 | *.userprefs
14 | *.user
15 | *.nupkg
16 | *.metaproj
17 | *.tmp
18 | *.log
19 | *.cache
20 | *.binlog
21 | *.zip
22 | __azurite*.*
23 | __*__
24 |
25 | .nuget
26 | *.lock.json
27 | *.nuget.props
28 | *.nuget.targets
29 |
30 | node_modules
31 | _site
32 | .jekyll-metadata
33 | .jekyll-cache
34 | Gemfile.lock
35 | package-lock.json
36 |
--------------------------------------------------------------------------------
/src/AssemblyFixture/build/xunit.assemblyfixture.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | <_Parameter1>Xunit.TestFramework
7 | <_Parameter2>xunit.assemblyfixture
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/assets/css/style.scss:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 |
4 | @import "jekyll-theme-slate";
5 |
6 | .inner {
7 | max-width: 960px;
8 | }
9 |
10 | pre, code {
11 | background-color: unset;
12 | font-size: unset;
13 | }
14 |
15 | code {
16 | font-size: 0.80em;
17 | }
18 |
19 | h1 > img {
20 | border: unset;
21 | box-shadow: unset;
22 | vertical-align: middle;
23 | -moz-box-shadow: unset;
24 | -o-box-shadow: unset;
25 | -ms-box-shadow: unset;
26 | }
27 |
--------------------------------------------------------------------------------
/src/AssemblyFixture/TestFramework.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using Xunit.Abstractions;
3 | using Xunit.Sdk;
4 |
5 | namespace Xunit;
6 |
7 | class TestFramework : XunitTestFramework
8 | {
9 | public TestFramework(IMessageSink messageSink)
10 | : base(messageSink)
11 | { }
12 |
13 | protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName)
14 | => new TestFrameworkExecutor(assemblyName, SourceInformationProvider, DiagnosticMessageSink);
15 | }
16 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # sln, csproj files (and friends) are always CRLF, even on linux
2 | *.sln text eol=crlf
3 | *.proj text eol=crlf
4 | *.csproj text eol=crlf
5 |
6 | # These are windows specific files which we may as well ensure are
7 | # always crlf on checkout
8 | *.bat text eol=crlf
9 | *.cmd text eol=crlf
10 |
11 | # Opt in known filetypes to always normalize line endings on checkin
12 | # and always use native endings on checkout
13 | *.c text
14 | *.config text
15 | *.h text
16 | *.cs text
17 | *.md text
18 | *.tt text
19 | *.txt text
20 |
21 | # Some must always be checked out as lf so enforce that for those files
22 | # If these are not lf then bash/cygwin on windows will not be able to
23 | # excute the files
24 | *.sh text eol=lf
--------------------------------------------------------------------------------
/.github/release.yml:
--------------------------------------------------------------------------------
1 | changelog:
2 | exclude:
3 | labels:
4 | - bydesign
5 | - dependencies
6 | - duplicate
7 | - question
8 | - invalid
9 | - wontfix
10 | - need info
11 | - docs
12 | - techdebt
13 | authors:
14 | - devlooped-bot
15 | - dependabot
16 | - github-actions
17 | categories:
18 | - title: ✨ Implemented enhancements
19 | labels:
20 | - enhancement
21 | - title: 🐛 Fixed bugs
22 | labels:
23 | - bug
24 | - title: 📝 Documentation updates
25 | labels:
26 | - docs
27 | - title: 🔨 Other
28 | labels:
29 | - '*'
30 | exclude:
31 | labels:
32 | - dependencies
33 |
--------------------------------------------------------------------------------
/.github/workflows/sponsor.yml:
--------------------------------------------------------------------------------
1 | name: sponsor 💜
2 | on:
3 | issues:
4 | types: [opened, edited, reopened]
5 | pull_request:
6 | types: [opened, edited, synchronize, reopened]
7 |
8 | jobs:
9 | sponsor:
10 | runs-on: ubuntu-latest
11 | continue-on-error: true
12 | env:
13 | token: ${{ secrets.GH_TOKEN }}
14 | if: ${{ !endsWith(github.event.sender.login, '[bot]') && !endsWith(github.event.sender.login, 'bot') }}
15 | steps:
16 | - name: 🤘 checkout
17 | if: env.token != ''
18 | uses: actions/checkout@v2
19 |
20 | - name: 💜 sponsor
21 | if: env.token != ''
22 | uses: devlooped/actions-sponsor@main
23 | with:
24 | token: ${{ env.token }}
25 |
--------------------------------------------------------------------------------
/src/Tests/AssemblyFixture.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0
4 | Xunit
5 | AssemblyFixture.Tests
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/AssemblyFixture/IAssemblyFixture.cs:
--------------------------------------------------------------------------------
1 | namespace Xunit;
2 |
3 |
4 | ///
5 | /// Used to decorate xUnit.net test classes and collections to indicate a test which has
6 | /// per-assembly fixture data. An instance of the fixture data is initialized just before
7 | /// the first test in the assembly is run, and if it implements IDisposable, is disposed
8 | /// after the last test in the assembly is run. To gain access to the fixture data from
9 | /// inside the test, a constructor argument should be added to the test class which
10 | /// exactly matches the .
11 | ///
12 | /// The type of the fixture.
13 | public interface IAssemblyFixture where TFixture : class, new() { }
14 |
--------------------------------------------------------------------------------
/src/AssemblyFixture/TestFrameworkExecutor.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Reflection;
3 | using Xunit.Abstractions;
4 | using Xunit.Sdk;
5 |
6 | namespace Xunit;
7 |
8 | class TestFrameworkExecutor : XunitTestFrameworkExecutor
9 | {
10 | public TestFrameworkExecutor(AssemblyName assemblyName, ISourceInformationProvider sourceInformationProvider, IMessageSink diagnosticMessageSink)
11 | : base(assemblyName, sourceInformationProvider, diagnosticMessageSink)
12 | { }
13 |
14 | protected override async void RunTestCases(IEnumerable testCases, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions)
15 | {
16 | using var assemblyRunner = new TestAssemblyRunner(TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, executionOptions);
17 | await assemblyRunner.RunAsync();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | # Builds a final release version and pushes to nuget.org
2 | # whenever a release is published.
3 | # Requires: secrets.NUGET_API_KEY
4 |
5 | name: publish
6 | on:
7 | release:
8 | types: [released]
9 |
10 | env:
11 | DOTNET_NOLOGO: true
12 | Configuration: Release
13 |
14 | jobs:
15 | publish:
16 | runs-on: ubuntu-latest
17 | steps:
18 | - name: 🤘 checkout
19 | uses: actions/checkout@v2
20 | with:
21 | submodules: recursive
22 | fetch-depth: 0
23 |
24 | - name: 🙏 build
25 | run: dotnet build -m:1 -p:version=${GITHUB_REF#refs/*/v}
26 |
27 | - name: 🧪 test
28 | uses: ./.github/workflows/test
29 |
30 | - name: 📦 pack
31 | run: dotnet pack -m:1 -p:version=${GITHUB_REF#refs/*/v}
32 |
33 | - name: 🚀 nuget
34 | run: dotnet nuget push ./bin/**/*.nupkg -s https://api.nuget.org/v3/index.json -k ${{secrets.NUGET_API_KEY}} --skip-duplicate
35 |
--------------------------------------------------------------------------------
/src/AssemblyFixture/AssemblyFixture.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0
4 | xunit.assemblyfixture
5 | Xunit
6 | xunit.assemblyfixture
7 | readme.md
8 |
9 | Assembly-level shared state via IAssemblyFixture, just like built-in collection and class fixture state.
10 |
11 | true
12 | false
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Daniel Cazzulino and Contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/.github/workflows/includes.yml:
--------------------------------------------------------------------------------
1 | name: +Mᐁ includes
2 | on:
3 | workflow_dispatch:
4 | push:
5 | branches:
6 | - 'main'
7 | paths:
8 | - '**.md'
9 | - '!changelog.md'
10 |
11 | jobs:
12 | includes:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: 🤖 defaults
16 | uses: devlooped/actions-bot@v1
17 | with:
18 | name: ${{ secrets.BOT_NAME }}
19 | email: ${{ secrets.BOT_EMAIL }}
20 | gh_token: ${{ secrets.GH_TOKEN }}
21 | github_token: ${{ secrets.GITHUB_TOKEN }}
22 |
23 | - name: 🤘 checkout
24 | uses: actions/checkout@v2
25 | with:
26 | token: ${{ env.GH_TOKEN }}
27 |
28 | - name: +Mᐁ includes
29 | uses: devlooped/actions-includes@v1
30 |
31 | - name: ✍ pull request
32 | uses: peter-evans/create-pull-request@v4
33 | with:
34 | base: main
35 | branch: markdown-includes
36 | delete-branch: true
37 | labels: docs
38 | author: ${{ env.BOT_AUTHOR }}
39 | committer: ${{ env.BOT_AUTHOR }}
40 | commit-message: +Mᐁ includes
41 | title: +Mᐁ includes
42 | body: +Mᐁ includes
43 | token: ${{ env.GH_TOKEN }}
44 |
--------------------------------------------------------------------------------
/.github/workflows/changelog.yml:
--------------------------------------------------------------------------------
1 | name: changelog
2 | on:
3 | workflow_dispatch:
4 | release:
5 | types: [released]
6 |
7 | jobs:
8 | changelog:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: 🤖 defaults
12 | uses: devlooped/actions-bot@v1
13 | with:
14 | name: ${{ secrets.BOT_NAME }}
15 | email: ${{ secrets.BOT_EMAIL }}
16 | gh_token: ${{ secrets.GH_TOKEN }}
17 | github_token: ${{ secrets.GITHUB_TOKEN }}
18 |
19 | - name: 🤘 checkout
20 | uses: actions/checkout@v2
21 | with:
22 | fetch-depth: 0
23 | ref: main
24 | token: ${{ env.GH_TOKEN }}
25 |
26 | - name: ⚙ ruby
27 | uses: ruby/setup-ruby@v1
28 | with:
29 | ruby-version: 3.0.3
30 |
31 | - name: ⚙ changelog
32 | run: |
33 | gem install github_changelog_generator
34 | github_changelog_generator --user ${GITHUB_REPOSITORY%/*} --project ${GITHUB_REPOSITORY##*/} --token $GH_TOKEN --o changelog.md --config-file .github/workflows/changelog.config
35 |
36 | - name: 🚀 changelog
37 | run: |
38 | git add changelog.md
39 | (git commit -m "🖉 Update changelog with ${GITHUB_REF#refs/*/}" && git push) || echo "Done"
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [v2.2.0](https://github.com/devlooped/xunit.assemblyfixture/tree/v2.2.0) (2023-04-17)
4 |
5 | [Full Changelog](https://github.com/devlooped/xunit.assemblyfixture/compare/v2.1.1...v2.2.0)
6 |
7 | :hammer: Other:
8 |
9 | - Support for .net core 2+ [\#2](https://github.com/devlooped/xunit.assemblyfixture/issues/2)
10 |
11 | ## [v2.1.1](https://github.com/devlooped/xunit.assemblyfixture/tree/v2.1.1) (2022-09-09)
12 |
13 | [Full Changelog](https://github.com/devlooped/xunit.assemblyfixture/compare/v2.1.0...v2.1.1)
14 |
15 | ## [v2.1.0](https://github.com/devlooped/xunit.assemblyfixture/tree/v2.1.0) (2022-09-08)
16 |
17 | [Full Changelog](https://github.com/devlooped/xunit.assemblyfixture/compare/v2.1.0-beta...v2.1.0)
18 |
19 | :hammer: Other:
20 |
21 | - Real time discovery [\#6](https://github.com/devlooped/xunit.assemblyfixture/issues/6)
22 |
23 | ## [v2.1.0-beta](https://github.com/devlooped/xunit.assemblyfixture/tree/v2.1.0-beta) (2022-09-07)
24 |
25 | [Full Changelog](https://github.com/devlooped/xunit.assemblyfixture/compare/cadb4b304851eaedaab39ad882bd56f211789edc...v2.1.0-beta)
26 |
27 | :twisted_rightwards_arrows: Merged:
28 |
29 | - README.md fixes [\#1](https://github.com/devlooped/xunit.assemblyfixture/pull/1) (@tpodolak)
30 |
31 |
32 |
33 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
34 |
--------------------------------------------------------------------------------
/src/Tests/Samples.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Xunit
4 | {
5 | public class Sample1 : IAssemblyFixture
6 | {
7 | MyAssemblyFixture fixture;
8 |
9 | // Fixtures are injectable into the test classes, just like with class and collection fixtures
10 | public Sample1(MyAssemblyFixture fixture)
11 | {
12 | this.fixture = fixture;
13 | }
14 |
15 | [Fact]
16 | public void EnsureSingleton()
17 | {
18 | Assert.Equal(1, MyAssemblyFixture.InstantiationCount);
19 | }
20 | }
21 |
22 | public class Sample2 : IAssemblyFixture
23 | {
24 | MyAssemblyFixture fixture;
25 |
26 | public Sample2(MyAssemblyFixture fixture)
27 | {
28 | this.fixture = fixture;
29 | }
30 |
31 | [Fact]
32 | public void EnsureSingleton()
33 | {
34 | Assert.Equal(1, MyAssemblyFixture.InstantiationCount);
35 | }
36 | }
37 |
38 | public class MyAssemblyFixture : IDisposable
39 | {
40 | public static int InstantiationCount;
41 |
42 | public MyAssemblyFixture()
43 | {
44 | InstantiationCount++;
45 | }
46 |
47 | public void Dispose()
48 | {
49 | // Uncomment this and it will surface as an assembly cleanup failure
50 | //throw new DivideByZeroException();
51 | //InstantiationCount = 0;
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/.github/workflows/test/action.yml:
--------------------------------------------------------------------------------
1 | name: test
2 | description: runs dotnet tests with retry
3 | runs:
4 | using: "composite"
5 | steps:
6 | - name: 🧪 test
7 | shell: bash --noprofile --norc {0}
8 | env:
9 | LC_ALL: en_US.utf8
10 | run: |
11 | [ -f .bash_profile ] && source .bash_profile
12 | counter=0
13 | exitcode=0
14 | reset="\e[0m"
15 | warn="\e[0;33m"
16 | while [ $counter -lt 6 ]
17 | do
18 | # run test and forward output also to a file in addition to stdout (tee command)
19 | if [ $filter ]
20 | then
21 | echo -e "${warn}Retry $counter for $filter ${reset}"
22 | dotnet test --no-build -m:1 --blame-hang --blame-hang-timeout 5m --filter=$filter | tee ./output.log
23 | else
24 | dotnet test --no-build -m:1 --blame-hang --blame-hang-timeout 5m | tee ./output.log
25 | fi
26 | # capture dotnet test exit status, different from tee
27 | exitcode=${PIPESTATUS[0]}
28 | if [ $exitcode == 0 ]
29 | then
30 | exit 0
31 | fi
32 | # cat output, get failed test names, remove trailing whitespace, sort+dedupe, join as FQN~TEST with |, remove trailing |.
33 | filter=$(cat ./output.log | grep -o -P '(?<=\sFailed\s)[\w\._]*' | sed 's/ *$//g' | sort -u | awk 'BEGIN { ORS="|" } { print("FullyQualifiedName~" $0) }' | grep -o -P '.*(?=\|$)')
34 | ((counter++))
35 | done
36 | exit $exitcode
37 |
--------------------------------------------------------------------------------
/src/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/AssemblyFixture/TestAssemblyRunner.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using Xunit.Abstractions;
7 | using Xunit.Sdk;
8 |
9 | namespace Xunit;
10 |
11 | class TestAssemblyRunner : XunitTestAssemblyRunner
12 | {
13 | readonly Dictionary assemblyFixtureMappings = new();
14 |
15 | public TestAssemblyRunner(ITestAssembly testAssembly,
16 | IEnumerable testCases,
17 | IMessageSink diagnosticMessageSink,
18 | IMessageSink executionMessageSink,
19 | ITestFrameworkExecutionOptions executionOptions)
20 | : base(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, executionOptions)
21 | {
22 | }
23 |
24 | protected override Task BeforeTestAssemblyFinishedAsync()
25 | {
26 | // Make sure we clean up everybody who is disposable, and use Aggregator.Run to isolate Dispose failures
27 | foreach (var disposable in assemblyFixtureMappings.Values.OfType())
28 | Aggregator.Run(disposable.Dispose);
29 |
30 | return base.BeforeTestAssemblyFinishedAsync();
31 | }
32 |
33 |
34 | protected override Task RunTestCollectionAsync(IMessageBus messageBus,
35 | ITestCollection testCollection,
36 | IEnumerable testCases,
37 | CancellationTokenSource cancellationTokenSource)
38 | => new TestCollectionRunner(assemblyFixtureMappings, testCollection, testCases, DiagnosticMessageSink, messageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), cancellationTokenSource).RunAsync();
39 | }
40 |
--------------------------------------------------------------------------------
/AssemblyFixture.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.3.32811.315
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssemblyFixture", "src\AssemblyFixture\AssemblyFixture.csproj", "{B2D6E6BF-63CC-4B36-BA99-F1743E7C5AE9}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssemblyFixture.Tests", "src\Tests\AssemblyFixture.Tests.csproj", "{8C6F430B-FA94-49D3-83E9-28C751211264}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F363283A-2BD8-4141-B573-9528B00ABC09}"
11 | ProjectSection(SolutionItems) = preProject
12 | readme.md = readme.md
13 | EndProjectSection
14 | EndProject
15 | Global
16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
17 | Debug|Any CPU = Debug|Any CPU
18 | Release|Any CPU = Release|Any CPU
19 | EndGlobalSection
20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
21 | {B2D6E6BF-63CC-4B36-BA99-F1743E7C5AE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 | {B2D6E6BF-63CC-4B36-BA99-F1743E7C5AE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 | {B2D6E6BF-63CC-4B36-BA99-F1743E7C5AE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {B2D6E6BF-63CC-4B36-BA99-F1743E7C5AE9}.Release|Any CPU.Build.0 = Release|Any CPU
25 | {8C6F430B-FA94-49D3-83E9-28C751211264}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {8C6F430B-FA94-49D3-83E9-28C751211264}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {8C6F430B-FA94-49D3-83E9-28C751211264}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {8C6F430B-FA94-49D3-83E9-28C751211264}.Release|Any CPU.Build.0 = Release|Any CPU
29 | EndGlobalSection
30 | GlobalSection(SolutionProperties) = preSolution
31 | HideSolutionNode = FALSE
32 | EndGlobalSection
33 | GlobalSection(ExtensibilityGlobals) = postSolution
34 | SolutionGuid = {9B1CBFD9-BF47-456C-818C-45D62F29C0DE}
35 | EndGlobalSection
36 | EndGlobal
37 |
--------------------------------------------------------------------------------
/src/AssemblyFixture/TestCollectionRunner.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using Xunit.Abstractions;
8 | using Xunit.Sdk;
9 |
10 | namespace Xunit;
11 |
12 | class TestCollectionRunner : XunitTestCollectionRunner
13 | {
14 | readonly Dictionary assemblyFixtureMappings;
15 | readonly IMessageSink diagnosticMessageSink;
16 |
17 | public TestCollectionRunner(Dictionary assemblyFixtureMappings,
18 | ITestCollection testCollection,
19 | IEnumerable testCases,
20 | IMessageSink diagnosticMessageSink,
21 | IMessageBus messageBus,
22 | ITestCaseOrderer testCaseOrderer,
23 | ExceptionAggregator aggregator,
24 | CancellationTokenSource cancellationTokenSource)
25 | : base(testCollection, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource)
26 | {
27 | this.assemblyFixtureMappings = assemblyFixtureMappings;
28 | this.diagnosticMessageSink = diagnosticMessageSink;
29 | }
30 |
31 | protected override Task RunTestClassAsync(ITestClass testClass, IReflectionTypeInfo @class, IEnumerable testCases)
32 | {
33 | foreach (var fixtureType in @class.Type.GetTypeInfo().ImplementedInterfaces
34 | .Where(i => i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == typeof(IAssemblyFixture<>))
35 | .Select(i => i.GetTypeInfo().GenericTypeArguments.Single())
36 | // First pass at filtering out before locking
37 | .Where(i => !assemblyFixtureMappings.ContainsKey(i)))
38 | {
39 | // ConcurrentDictionary's GetOrAdd does not lock around the value factory call, so we need
40 | // to do it ourselves.
41 | lock (assemblyFixtureMappings)
42 | if (!assemblyFixtureMappings.ContainsKey(fixtureType))
43 | Aggregator.Run(() => assemblyFixtureMappings.Add(fixtureType, Activator.CreateInstance(fixtureType)));
44 | }
45 |
46 | // Don't want to use .Concat + .ToDictionary because of the possibility of overriding types,
47 | // so instead we'll just let collection fixtures override assembly fixtures.
48 | var combinedFixtures = new Dictionary(assemblyFixtureMappings);
49 | foreach (var kvp in CollectionFixtureMappings)
50 | combinedFixtures[kvp.Key] = kvp.Value;
51 |
52 | // We've done everything we need, so let the built-in types do the rest of the heavy lifting
53 | return new XunitTestClassRunner(testClass, @class, testCases, diagnosticMessageSink, MessageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), CancellationTokenSource, combinedFixtures).RunAsync();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # Builds and runs tests in all three supported OSes
2 | # Pushes CI feed if secrets.SLEET_CONNECTION is provided
3 |
4 | name: build
5 | on:
6 | workflow_dispatch:
7 | push:
8 | branches: [ main, dev, 'dev/*', 'feature/*', 'rel/*' ]
9 | paths-ignore:
10 | - changelog.md
11 | - code-of-conduct.md
12 | - security.md
13 | - support.md
14 | - readme.md
15 | pull_request:
16 | types: [opened, synchronize, reopened]
17 |
18 | env:
19 | DOTNET_NOLOGO: true
20 | VersionPrefix: 42.42.${{ github.run_number }}
21 | VersionLabel: ${{ github.ref }}
22 |
23 | defaults:
24 | run:
25 | shell: bash
26 |
27 | jobs:
28 | os-matrix:
29 | runs-on: ubuntu-latest
30 | outputs:
31 | matrix: ${{ steps.lookup.outputs.matrix }}
32 | steps:
33 | - name: 🤘 checkout
34 | uses: actions/checkout@v2
35 |
36 | - name: 🔎 lookup
37 | id: lookup
38 | shell: pwsh
39 | run: |
40 | $path = './.github/workflows/os-matrix.json'
41 | $os = if (test-path $path) { cat $path } else { '["ubuntu-latest"]' }
42 | echo "matrix=$os" >> $env:GITHUB_OUTPUT
43 |
44 | build:
45 | needs: os-matrix
46 | name: build-${{ matrix.os }}
47 | runs-on: ${{ matrix.os }}
48 | strategy:
49 | matrix:
50 | os: ${{ fromJSON(needs.os-matrix.outputs.matrix) }}
51 | steps:
52 | - name: 🤘 checkout
53 | uses: actions/checkout@v2
54 | with:
55 | submodules: recursive
56 | fetch-depth: 0
57 |
58 | - name: 🙏 build
59 | run: dotnet build -m:1
60 |
61 | - name: ⚙ GNU grep
62 | if: matrix.os == 'macOS-latest'
63 | run: |
64 | brew install grep
65 | echo 'export PATH="/usr/local/opt/grep/libexec/gnubin:$PATH"' >> .bash_profile
66 |
67 | - name: 🧪 test
68 | uses: ./.github/workflows/test
69 |
70 | - name: 📦 pack
71 | run: dotnet pack -m:1
72 |
73 | # Only push CI package to sleet feed if building on ubuntu (fastest)
74 | - name: 🚀 sleet
75 | env:
76 | SLEET_CONNECTION: ${{ secrets.SLEET_CONNECTION }}
77 | if: env.SLEET_CONNECTION != ''
78 | run: |
79 | dotnet tool install -g --version 4.0.18 sleet
80 | sleet push bin --config none -f --verbose -p "SLEET_FEED_CONTAINER=nuget" -p "SLEET_FEED_CONNECTIONSTRING=${{ secrets.SLEET_CONNECTION }}" -p "SLEET_FEED_TYPE=azure" || echo "No packages found"
81 |
82 | dotnet-format:
83 | runs-on: ubuntu-latest
84 | steps:
85 | - name: 🤘 checkout
86 | uses: actions/checkout@v2
87 | with:
88 | submodules: recursive
89 | fetch-depth: 0
90 |
91 | - name: ✓ ensure format
92 | run: |
93 | dotnet format whitespace --verify-no-changes -v:diag --exclude ~/.nuget
94 | dotnet format style --verify-no-changes -v:diag --exclude ~/.nuget
95 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome:http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Don't use tabs for indentation.
7 | [*]
8 | indent_style = space
9 | # (Please don't specify an indent_size here; that has too many unintended consequences.)
10 |
11 | # Code files
12 | [*.{cs,csx,vb,vbx}]
13 | indent_size = 4
14 |
15 | # Xml project files
16 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,msbuildproj,props,targets}]
17 | indent_size = 2
18 |
19 | # Xml config files
20 | [*.{ruleset,config,nuspec,resx,vsixmanifest,vsct}]
21 | indent_size = 2
22 |
23 | # YAML files
24 | [*.{yaml,yml}]
25 | indent_size = 2
26 |
27 | # JSON files
28 | [*.json]
29 | indent_size = 2
30 |
31 | # Dotnet code style settings:
32 | [*.{cs,vb}]
33 | # Sort using and Import directives with System.* appearing first
34 | dotnet_sort_system_directives_first = true
35 | # Avoid "this." and "Me." if not necessary
36 | dotnet_style_qualification_for_field = false:suggestion
37 | dotnet_style_qualification_for_property = false:suggestion
38 | dotnet_style_qualification_for_method = false:suggestion
39 | dotnet_style_qualification_for_event = false:suggestion
40 |
41 | # Use language keywords instead of framework type names for type references
42 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
43 | dotnet_style_predefined_type_for_member_access = true:suggestion
44 |
45 | # Suggest more modern language features when available
46 | dotnet_style_object_initializer = true:suggestion
47 | dotnet_style_collection_initializer = true:suggestion
48 | dotnet_style_coalesce_expression = true:suggestion
49 | dotnet_style_null_propagation = true:suggestion
50 | dotnet_style_explicit_tuple_names = true:suggestion
51 |
52 | # CSharp code style settings:
53 |
54 | # IDE0040: Add accessibility modifiers
55 | dotnet_style_require_accessibility_modifiers = omit_if_default:error
56 |
57 | # IDE0040: Add accessibility modifiers
58 | dotnet_diagnostic.IDE0040.severity = error
59 |
60 | [*.cs]
61 | # Top-level files are definitely OK
62 | csharp_using_directive_placement = outside_namespace:silent
63 | csharp_style_namespace_declarations = block_scoped:silent
64 | csharp_prefer_simple_using_statement = true:suggestion
65 | csharp_prefer_braces = true:silent
66 |
67 | # Prefer "var" everywhere
68 | csharp_style_var_for_built_in_types = true:suggestion
69 | csharp_style_var_when_type_is_apparent = true:suggestion
70 | csharp_style_var_elsewhere = true:suggestion
71 |
72 | # Prefer method-like constructs to have an expression-body
73 | csharp_style_expression_bodied_methods = true:none
74 | csharp_style_expression_bodied_constructors = true:none
75 | csharp_style_expression_bodied_operators = true:none
76 |
77 | # Prefer property-like constructs to have an expression-body
78 | csharp_style_expression_bodied_properties = true:none
79 | csharp_style_expression_bodied_indexers = true:none
80 | csharp_style_expression_bodied_accessors = true:none
81 |
82 | # Suggest more modern language features when available
83 | csharp_style_pattern_matching_over_is_with_cast_check = true:error
84 | csharp_style_pattern_matching_over_as_with_null_check = true:error
85 | csharp_style_inlined_variable_declaration = true:suggestion
86 | csharp_style_throw_expression = true:suggestion
87 | csharp_style_conditional_delegate_call = true:suggestion
88 |
89 | # Newline settings
90 | csharp_new_line_before_open_brace = all
91 | csharp_new_line_before_else = true
92 | csharp_new_line_before_catch = true
93 | csharp_new_line_before_finally = true
94 | csharp_new_line_before_members_in_object_initializers = true
95 | csharp_new_line_before_members_in_anonymous_types = true
96 |
97 | # Test settings
98 | [**/*Tests*/**{.cs,.vb}]
99 | # xUnit1013: Public method should be marked as test. Allows using records as test classes
100 | dotnet_diagnostic.xUnit1013.severity = none
101 |
102 | # Default severity for analyzer diagnostics with category 'Style'
103 | dotnet_analyzer_diagnostic.category-Style.severity = none
104 |
105 | # VSTHRD200: Use "Async" suffix for async methods
106 | dotnet_diagnostic.VSTHRD200.severity = none
107 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # xunit.assemblyfixture
2 |
3 | Provides shared state/fixture data across tests in the same assembly, following the design of
4 | [class fixtures](https://xunit.github.io/docs/shared-context.html#class-fixture) (rather than the more
5 | convoluted [collection fixtures](https://xunit.github.io/docs/shared-context.html#collection-fixture)).
6 |
7 | To complement [xUnit documentation style](https://xunit.github.io/docs/shared-context.html), I shamelessly copy its layout here.
8 |
9 | > NOTE: applies to xunit 2.4 only. 3.0+ will have its own way of doing this.
10 |
11 | ## Shared Context between Tests
12 |
13 | Please read [xUnit documentation](https://xunit.github.io/docs/shared-context.html) on shared context and the various built-in options, which are:
14 |
15 | - [Constructor and Dispose](https://xunit.github.io/docs/shared-context.html#constructor) (shared setup/cleanup code without sharing object instances)
16 | - [Class Fixtures](https://xunit.github.io/docs/shared-context.html#class-fixture) (shared object instance across tests in a single class)
17 | - [Collection Fixtures](https://xunit.github.io/docs/shared-context.html#collection-fixture) (shared object instances across multiple test classes
18 |
19 | To which this project adds:
20 |
21 | - Assembly Fixtures (shared object instances across multiple test classes within the same test assembly)
22 |
23 | ### Assembly Fixtures
24 |
25 | ***When to use***: when you want to create a single assembly-level context
26 | and share it among all tests in the assembly, and have it cleaned up after
27 | all the tests in the assembly have finished.
28 |
29 | Sometimes test context creation and cleanup can be very expensive. If you were
30 | to run the creation and cleanup code during every test, it might make the tests
31 | slower than you want. Sometimes, you just need to aggregate data across multiple
32 | tests in multiple classes. You can use the *assembly fixture* feature of
33 | [xUnit.net [Assembly Fixtures]](https://www.nuget.org/packages/xunit.assemblyfixture)
34 | to share a single object instance among all tests in a test assembly.
35 |
36 | When using an assembly fixture, xUnit.net will ensure that the fixture instance
37 | will be created before any of the tests using it have run, and once all the tests
38 | have finished, it will clean up the fixture object by calling `Dispose`, if present.
39 |
40 | To use assembly fixtures, you need to take the following steps:
41 |
42 | - Create the fixture class, and put the the startup code in the fixture
43 | class constructor.
44 | - If the fixture class needs to perform cleanup, implement `IDisposable`
45 | on the fixture class, and put the cleanup code in the `Dispose()` method.
46 | - Add `IAssemblyFixture` to the test class.
47 | - If the test class needs access to the fixture instance, add it as a
48 | constructor argument, and it will be provided automatically.
49 |
50 | Here is a simple example:
51 |
52 | ```csharp
53 | public class DatabaseFixture : IDisposable
54 | {
55 | public DatabaseFixture()
56 | {
57 | Db = new SqlConnection("MyConnectionString");
58 |
59 | // ... initialize data in the test database ...
60 | }
61 |
62 | public void Dispose()
63 | {
64 | // ... clean up test data from the database ...
65 | }
66 |
67 | public SqlConnection Db { get; private set; }
68 | }
69 |
70 | public class MyDatabaseTests : IAssemblyFixture
71 | {
72 | DatabaseFixture fixture;
73 |
74 | public MyDatabaseTests(DatabaseFixture fixture)
75 | {
76 | this.fixture = fixture;
77 | }
78 |
79 | // ... write tests, using fixture.Db to get access to the SQL Server ...
80 | }
81 | ```
82 |
83 | Just before the first test in the assembly to require the `DatabaseFixture`
84 | is run, xUnit.net will create an instance of `DatabaseFixture`. Each subsequent
85 | test will receive the same shared instance, passed to the constructor of
86 | `MyDatabaseTests`, just like a static singleton, but with predictable cleanup
87 | via `IDisposable`.
88 |
89 | ***Important note:*** xUnit.net uses the presence of the interface
90 | `IAssemblyFixture<>` to know that you want an assembly fixture to
91 | be created and cleaned up. It will do this whether you take the instance of
92 | the class as a constructor argument or not. Simiarly, if you add the constructor
93 | argument but forget to add the interface, xUnit.net will let you know that it
94 | does not know how to satisfy the constructor argument.
95 |
96 | If you need multiple fixture objects, you can implement the interface as many
97 | times as you want, and add constructor arguments for whichever of the fixture
98 | object instances you need access to. The order of the constructor arguments
99 | is unimportant.
100 |
101 | Note that you cannot control the order that fixture objects are created, and
102 | fixtures cannot take dependencies on other fixtures. If you have need to
103 | control creation order and/or have dependencies between fixtures, you should
104 | create a class which encapsulates the other two fixtures, so that it can
105 | do the object creation itself.
--------------------------------------------------------------------------------
/.netconfig:
--------------------------------------------------------------------------------
1 | [file]
2 | url = https://github.com/devlooped/oss
3 | [file ".netconfig"]
4 | url = https://github.com/devlooped/oss/blob/main/.netconfig
5 | skip
6 | [file "readme.md"]
7 | url = https://github.com/devlooped/oss/blob/main/readme.md
8 | skip
9 | [file "src/icon.png"]
10 | url = https://github.com/devlooped/oss/blob/main/src/icon.png
11 | skip
12 | [file ".github/ISSUE_TEMPLATE/config.yml"]
13 | url = https://github.com/devlooped/oss/blob/main/.github/ISSUE_TEMPLATE/config.yml
14 | skip
15 | [file ".github/workflows/release-artifacts.yml"]
16 | url = https://github.com/devlooped/oss/blob/main/.github/workflows/release-artifacts.yml
17 | skip
18 | [file "assets/images/sponsors.svg"]
19 | url = https://github.com/devlooped/oss/blob/main/assets/images/sponsors.svg
20 | skip
21 | [file "assets/images/sponsors.png"]
22 | url = https://github.com/devlooped/oss/blob/main/assets/images/sponsors.png
23 | skip
24 | [file ".editorconfig"]
25 | url = https://github.com/devlooped/oss/blob/main/.editorconfig
26 | sha = fd5b554bf3538a3c92a0b49e395c8ad2e8429158
27 | etag = bf02d1679442e5169d03304164b8e9407997ac3132145831a91ba61fc8b50687
28 | weak
29 | [file ".gitattributes"]
30 | url = https://github.com/devlooped/oss/blob/main/.gitattributes
31 | sha = 0683ee777d7d878d4bf013d7deea352685135a05
32 | etag = 7acb32f5fa6d4ccd9c824605a7c2b8538497f0068c165567807d393dcf4d6bb7
33 | weak
34 | [file ".github/dependabot.yml"]
35 | url = https://github.com/devlooped/oss/blob/main/.github/dependabot.yml
36 | sha = 4f070a477b4162a280f02722ae666376ae4fcc71
37 | etag = 35f2134fff3b0235ff8dac8618a76198c8ef533ad2f29628bbb435cd1134d638
38 | weak
39 | [file ".github/workflows/build.yml"]
40 | url = https://github.com/devlooped/oss/blob/main/.github/workflows/build.yml
41 | sha = 13d67e2cf3f786c8189364fd29332aaa7dc575dc
42 | etag = c616df0877fba60002ccfc0397e9f731ddb22acbbb195a0598fedd4cac5f3135
43 | weak
44 | [file ".github/workflows/changelog.yml"]
45 | url = https://github.com/devlooped/oss/blob/main/.github/workflows/changelog.yml
46 | sha = a4b66eb5f4dfb9704502f19f59ba33cb4855188c
47 | etag = 54c0b571648b1055beb3ddac180b34e93a9869b9f0277de306901b2c1dbe0b2c
48 | weak
49 | [file ".github/workflows/dotnet-file.yml"]
50 | url = https://github.com/devlooped/oss/blob/main/.github/workflows/dotnet-file.yml
51 | sha = f08c3f28e46e28eb31e70846d65e57aa9553ce56
52 | etag = 567444486383d032c1c5fbc538f07e860f92b1d08c66ac6ffb1db64ca539251c
53 | weak
54 | [file ".github/workflows/includes.yml"]
55 | url = https://github.com/devlooped/oss/blob/main/.github/workflows/includes.yml
56 | sha = ac753b791d03997eb655efb26ae141b51addd1c0
57 | etag = fcd94a08ac9ebc0e8351deac4e7f085cf8ef67816cc50006e068f44166096eb8
58 | weak
59 | [file ".github/workflows/publish.yml"]
60 | url = https://github.com/devlooped/oss/blob/main/.github/workflows/publish.yml
61 | sha = d3022567c9ef2bc9461511e53b8abe065afdf03b
62 | etag = 58601b5a71c805647ab26e84053acdfb8d174eaa93330487af8a5503753c5707
63 | weak
64 | [file ".github/workflows/test/action.yml"]
65 | url = https://github.com/devlooped/oss/blob/main/.github/workflows/test/action.yml
66 | sha = 9a1b07589b9bde93bc12528e9325712a32dec418
67 | etag = b54216ac431a83ce5477828d391f02046527e7f6fffd21da1d03324d352c3efb
68 | weak
69 | [file ".gitignore"]
70 | url = https://github.com/devlooped/oss/blob/main/.gitignore
71 | sha = b87a8a795a4c2b6830602225c066c11108552a99
72 | etag = 96e0860052044780f1fc9e3bdfbee09d82d5dddb8b1217d67460fc7330a64dd8
73 | weak
74 | [file "Directory.Build.rsp"]
75 | url = https://github.com/devlooped/oss/blob/main/Directory.Build.rsp
76 | sha = ae25fae9d7daf0cb47d537ba870914aa3052f0c9
77 | etag = 6a6c6e1d3895df953abf14c82b0899e3eea75cdcd679f6212dcfea15183d73d6
78 | weak
79 | [file "_config.yml"]
80 | url = https://github.com/devlooped/oss/blob/main/_config.yml
81 | sha = fa83a5161ba52bc5d510ce0ba75ee0b1f8d4bc63
82 | etag = 9139148f845adf503fd3c3c140eb64421fc476a1f9c027fc50825c0efb05f557
83 | weak
84 | [file "assets/css/style.scss"]
85 | url = https://github.com/devlooped/oss/blob/main/assets/css/style.scss
86 | sha = 9db26e2710b084d219d6355339d822f159bf5780
87 | etag = f710d8919abfd5a8d00050b74ba7d0bb05c6d02e40842a3012eb96555c208504
88 | weak
89 | [file "license.txt"]
90 | url = https://github.com/devlooped/oss/blob/main/license.txt
91 | sha = 0683ee777d7d878d4bf013d7deea352685135a05
92 | etag = 2c6335b37e4ae05eea7c01f5d0c9d82b49c488f868a8b5ba7bff7c6ff01f3994
93 | weak
94 | [file "src/Directory.Build.props"]
95 | url = https://github.com/devlooped/oss/blob/main/src/Directory.Build.props
96 | sha = 6ae80a175a8f926ac5d9ffb0f6afd55d85cc9320
97 | etag = 69d4b16c14d5047b3ed812dbf556b0b8d77deb86f73af04b9bd3640220056fa8
98 | weak
99 | [file "src/Directory.Build.targets"]
100 | url = https://github.com/devlooped/oss/blob/main/src/Directory.Build.targets
101 | sha = 1514d15399a7d545ad92a0e9d57dc8295fdd6af8
102 | etag = 428f80b0786ff17b836c7a5b0640948724855d17933e958642b22849ac00dadb
103 | weak
104 | [file "src/kzu.snk"]
105 | url = https://github.com/devlooped/oss/blob/main/src/kzu.snk
106 | skip
107 | [file "src/nuget.config"]
108 | url = https://github.com/devlooped/oss/blob/main/src/nuget.config
109 | sha = b2fa09bd9db6de89e37a8ba6705b5659e435dafd
110 | etag = eb2d09e546aa1e11c0b464d9ed6ab2d3c028a1d86c3ac40a318053625fb72819
111 | weak
112 | [file ".github/release.yml"]
113 | url = https://github.com/devlooped/oss/blob/main/.github/release.yml
114 | sha = 1afd173fe8f81b510c597737b0d271218e81fa73
115 | etag = 482dc2c892fc7ce0cb3a01eb5d9401bee50ddfb067d8cb85873555ce63cf5438
116 | weak
117 | [file ".github/workflows/changelog.config"]
118 | url = https://github.com/devlooped/oss/blob/main/.github/workflows/changelog.config
119 | sha = 055a8b7c94b74ae139cce919d60b83976d2a9942
120 | etag = ddb17acb5872e9e69a76f9dec0ca590f25382caa2ccf750df058dcabb674db2b
121 | weak
122 | [file ".github/workflows/combine-prs.yml"]
123 | url = https://github.com/devlooped/oss/blob/main/.github/workflows/combine-prs.yml
124 | sha = c1610886eba42cb250e3894aed40c0a258cd383d
125 | etag = 598ee294649a44d4c5d5934416c01183597d08aa7db7938453fd2bbf52a4e64d
126 | weak
127 | [file ".github/workflows/sponsor.yml"]
128 | url = https://github.com/devlooped/oss/blob/main/.github/workflows/sponsor.yml
129 | sha = 8990ebb36199046e0b8098bad9e46dcef739c56e
130 | etag = e1dc114d2e8b57d50649989d32dbf0c9080ec77da3738a4cc79e9256d6ca5d3e
131 | weak
132 |
--------------------------------------------------------------------------------
/.github/workflows/combine-prs.yml:
--------------------------------------------------------------------------------
1 | # Source: https://github.com/hrvey/combine-prs-workflow
2 | # Tweaks: regex support for branch
3 |
4 | name: '⛙ combine-prs'
5 |
6 | on:
7 | workflow_dispatch:
8 | inputs:
9 | branchExpression:
10 | description: 'Regular expression to match against PR branches to find combinable PRs'
11 | required: true
12 | default: 'dependabot'
13 | mustBeGreen:
14 | description: 'Only combine PRs that are green (status is success)'
15 | required: true
16 | default: true
17 | combineTitle:
18 | description: 'Title of the combined PR'
19 | required: true
20 | default: '⬆️ Bump dependencies'
21 | combineBranchName:
22 | description: 'Name of the branch to combine PRs into'
23 | required: true
24 | default: 'combine-prs'
25 | ignoreLabel:
26 | description: 'Exclude PRs with this label'
27 | required: true
28 | default: 'nocombine'
29 |
30 | jobs:
31 | combine-prs:
32 | name: ${{ github.event.inputs.combineBranchName }}
33 | runs-on: ubuntu-latest
34 | steps:
35 | - uses: actions/github-script@v6
36 | with:
37 | github-token: ${{secrets.GITHUB_TOKEN}}
38 | script: |
39 | const pulls = await github.paginate('GET /repos/:owner/:repo/pulls', {
40 | owner: context.repo.owner,
41 | repo: context.repo.repo
42 | });
43 | const branchRegExp = new RegExp(`${{github.event.inputs.branchExpression}}`);
44 | let branchesAndPRStrings = [];
45 | let baseBranch = null;
46 | let baseBranchSHA = null;
47 | for (const pull of pulls) {
48 | const branch = pull['head']['ref'];
49 | console.log('Pull for branch: ' + branch);
50 | if (branchRegExp.test(branch)) {
51 | console.log('Branch matched: ' + branch);
52 | let statusOK = true;
53 | if(${{ github.event.inputs.mustBeGreen }}) {
54 | console.log('Checking green status: ' + branch);
55 | const stateQuery = `query($owner: String!, $repo: String!, $pull_number: Int!) {
56 | repository(owner: $owner, name: $repo) {
57 | pullRequest(number:$pull_number) {
58 | commits(last: 1) {
59 | nodes {
60 | commit {
61 | statusCheckRollup {
62 | state
63 | }
64 | }
65 | }
66 | }
67 | }
68 | }
69 | }`
70 | const vars = {
71 | owner: context.repo.owner,
72 | repo: context.repo.repo,
73 | pull_number: pull['number']
74 | };
75 | const result = await github.graphql(stateQuery, vars);
76 | const [{ commit }] = result.repository.pullRequest.commits.nodes;
77 | const state = commit.statusCheckRollup.state
78 | console.log('Validating status: ' + state);
79 | if(state != 'SUCCESS') {
80 | console.log('Discarding ' + branch + ' with status ' + state);
81 | statusOK = false;
82 | }
83 | }
84 | console.log('Checking labels: ' + branch);
85 | const labels = pull['labels'];
86 | for(const label of labels) {
87 | const labelName = label['name'];
88 | console.log('Checking label: ' + labelName);
89 | if(labelName == '${{ github.event.inputs.ignoreLabel }}') {
90 | console.log('Discarding ' + branch + ' with label ' + labelName);
91 | statusOK = false;
92 | }
93 | }
94 | if (statusOK) {
95 | console.log('Adding branch to array: ' + branch);
96 | const prString = '#' + pull['number'] + ' ' + pull['title'];
97 | branchesAndPRStrings.push({ branch, prString });
98 | baseBranch = pull['base']['ref'];
99 | baseBranchSHA = pull['base']['sha'];
100 | }
101 | }
102 | }
103 | if (branchesAndPRStrings.length == 0) {
104 | core.setFailed('No PRs/branches matched criteria');
105 | return;
106 | }
107 | if (branchesAndPRStrings.length == 1) {
108 | core.setFailed('Only one PR/branch matched criteria');
109 | return;
110 | }
111 |
112 | try {
113 | await github.rest.git.createRef({
114 | owner: context.repo.owner,
115 | repo: context.repo.repo,
116 | ref: 'refs/heads/' + '${{ github.event.inputs.combineBranchName }}',
117 | sha: baseBranchSHA
118 | });
119 | } catch (error) {
120 | console.log(error);
121 | core.setFailed('Failed to create combined branch - maybe a branch by that name already exists?');
122 | return;
123 | }
124 |
125 | let combinedPRs = [];
126 | let mergeFailedPRs = [];
127 | for(const { branch, prString } of branchesAndPRStrings) {
128 | try {
129 | await github.rest.repos.merge({
130 | owner: context.repo.owner,
131 | repo: context.repo.repo,
132 | base: '${{ github.event.inputs.combineBranchName }}',
133 | head: branch,
134 | });
135 | console.log('Merged branch ' + branch);
136 | combinedPRs.push(prString);
137 | } catch (error) {
138 | console.log('Failed to merge branch ' + branch);
139 | mergeFailedPRs.push(prString);
140 | }
141 | }
142 |
143 | console.log('Creating combined PR');
144 | const combinedPRsString = combinedPRs.join('\n');
145 | let body = '⛙ Combined PRs:\n' + combinedPRsString;
146 | if(mergeFailedPRs.length > 0) {
147 | const mergeFailedPRsString = mergeFailedPRs.join('\n');
148 | body += '\n\n⚠️ The following PRs were left out due to merge conflicts:\n' + mergeFailedPRsString
149 | }
150 | await github.rest.pulls.create({
151 | owner: context.repo.owner,
152 | repo: context.repo.repo,
153 | title: '⛙ ${{github.event.inputs.combineTitle}}',
154 | head: '${{ github.event.inputs.combineBranchName }}',
155 | base: baseBranch,
156 | body: body
157 | });
158 |
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | false
6 |
7 | true
14 |
15 |
16 |
17 |
18 | $(CI)
19 |
20 |
21 |
22 | Daniel Cazzulino
23 | Copyright (C) Daniel Cazzulino and Contributors. All rights reserved.
24 | false
25 | MIT
26 |
27 |
28 | icon.png
29 | readme.md
30 |
31 | icon.png
32 | readme.md
33 |
34 | true
35 | true
36 |
37 | $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\bin'))
38 |
39 |
40 | true
41 |
42 |
43 | true
44 |
45 |
46 |
47 | Release
48 | true
49 | false
50 | Latest
51 |
52 |
53 | false
54 |
55 | embedded
56 | true
57 | enable
58 |
59 | strict
60 |
61 |
62 | $(MSBuildProjectName)
63 | $(MSBuildProjectName.IndexOf('.'))
64 | $(MSBuildProjectName.Substring(0, $(RootNamespaceDot)))
65 |
66 |
67 | $(DefaultItemExcludes);*.binlog;*.zip;*.rsp;*.items;**/TestResults/**/*.*
68 |
69 | true
70 | true
71 | true
72 | true
73 |
74 |
75 | true
76 |
77 |
78 | false
79 |
80 |
81 | NU5105;$(NoWarn)
82 |
83 | true
84 |
85 |
86 | true
87 |
88 |
89 | LatestMinor
90 |
91 |
92 |
93 |
94 | $(MSBuildThisFileDirectory)kzu.snk
95 |
101 | 002400000480000094000000060200000024000052534131000400000100010051155fd0ee280be78d81cc979423f1129ec5dd28edce9cd94fd679890639cad54c121ebdb606f8659659cd313d3b3db7fa41e2271158dd602bb0039a142717117fa1f63d93a2d288a1c2f920ec05c4858d344a45d48ebd31c1368ab783596b382b611d8c92f9c1b3d338296aa21b12f3bc9f34de87756100c172c52a24bad2db
102 | 00352124762f2aa5
103 | true
104 |
105 |
106 |
107 |
115 | 42.42.42
116 |
117 |
118 |
119 | <_VersionLabel>$(VersionLabel.Replace('refs/heads/', ''))
120 |
121 | <_VersionLabel Condition="$(_VersionLabel.Contains('refs/pull/'))">$(VersionLabel.TrimEnd('.0123456789'))
122 |
123 | <_VersionLabel>$(_VersionLabel.Replace('refs/pull/', 'pr'))
124 |
125 | <_VersionLabel>$(_VersionLabel.Replace('/merge', ''))
126 |
127 | <_VersionLabel>$(_VersionLabel.Replace('/', '-'))
128 |
129 |
130 | $(_VersionLabel)
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
--------------------------------------------------------------------------------
/src/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CI;$(DefineConstants)
6 |
7 |
8 |
9 | true
10 | true
11 |
12 |
13 |
14 |
24 | false
25 | true
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
39 |
40 |
41 |
44 |
45 |
48 |
49 |
50 |
52 |
53 | 1.0.0
54 | $(VersionPrefix)-$(VersionSuffix)
55 | $(VersionPrefix)
56 |
57 |
58 |
59 |
60 | $(PackFolder)
61 | $(PackFolderPath.Replace('\$(TargetFramework)', ''))
62 | $(IntermediateOutputPath)$(PackFolderPath)\
63 | $(OutputPath)$(PackFolderPath)\
64 | $(OutputPath)
65 |
66 |
67 |
68 |
69 | pr$(GITHUB_REF.Replace('refs/pull/', '').Replace('/merge', ''))
70 | $(GITHUB_REF.Replace('refs/heads/', '').Replace('refs/tags/', ''))
71 |
72 | $(BUILD_SOURCEBRANCH.Replace('refs/heads/', '').Replace('refs/tags/', ''))
73 |
74 | pr$(APPVEYOR_PULL_REQUEST_NUMBER)
75 | $(APPVEYOR_REPO_TAG_NAME)
76 | $(APPVEYOR_REPO_BRANCH)
77 |
78 | $(TEAMCITY_BUILD_BRANCH)
79 |
80 | pr$(TRAVIS_PULL_REQUEST)
81 | $(TRAVIS_BRANCH)
82 |
83 | pr$(CIRCLE_PR_NUMBER)
84 | $(CIRCLE_TAG)
85 | $(CIRCLE_BRANCH)
86 |
87 | $(CI_COMMIT_TAG)
88 | pr$(CI_MERGE_REQUEST_IID)
89 | pr$(CI_EXTERNAL_PULL_REQUEST_IID)
90 | $(CI_COMMIT_BRANCH)
91 |
92 | pr$(BUDDY_EXECUTION_PULL_REQUEST_NO)
93 | $(BUDDY_EXECUTION_TAG)
94 | $(BUDDY_EXECUTION_BRANCH)
95 |
96 |
97 |
98 |
99 | PrepareResources;$(CoreCompileDependsOn)
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | MSBuild:Compile
110 | $(IntermediateOutputPath)\$([MSBuild]::ValueOrDefault('%(RelativeDir)', '').Replace('\', '.').Replace('/', '.'))%(Filename).g$(DefaultLanguageSourceExtension)
111 | $(Language)
112 | $(RootNamespace)
113 | $(RootNamespace).$([MSBuild]::ValueOrDefault('%(RelativeDir)', '').Replace('\', '.').Replace('/', '.').TrimEnd('.'))
114 | %(Filename)
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
138 |
139 |
140 |
141 | $(PrivateRepositoryUrl)
142 |
143 |
144 |
145 | $(SourceRevisionId)
146 | $(SourceRevisionId.Substring(0, 9))
147 |
148 | $(RepositorySha)
149 |
150 |
151 |
152 |
153 | <_GitSourceRoot Include="@(SourceRoot -> WithMetadataValue('SourceControl', 'git'))" />
154 |
155 |
156 |
157 | @(_GitSourceRoot)
158 |
159 |
160 |
161 |
162 |
167 |
168 | $(RepositoryUrl)
169 | $(Description)
170 | $(RepositoryUrl)/blob/main/changelog.md
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
--------------------------------------------------------------------------------