diff --git a/.gitattributes b/.gitattributes index 4f89148..3095556 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,7 @@ # normalize by default * text=auto encoding=UTF-8 *.sh text eol=lf +*.sbn eol=lf # These are windows specific files which we may as well ensure are # always crlf on checkout diff --git a/.github/actions/dotnet/action.yml b/.github/actions/dotnet/action.yml index fa7957a..c876ae3 100644 --- a/.github/actions/dotnet/action.yml +++ b/.github/actions/dotnet/action.yml @@ -8,11 +8,28 @@ runs: id: dotnet shell: bash run: | - VERSIONS=$(gh api /repos/${{ github.repository }}/properties/values | jq -r '.[] | select(.property_name == "DOTNET") | .value') - echo "versions=$VERSIONS" >> $GITHUB_OUTPUT + VERSIONS=$(gh api repos/${{ github.repository }}/properties/values | jq -r '.[] | select(.property_name == "DOTNET") | .value') + # Remove extra whitespace from VERSIONS + VERSIONS=$(echo "$VERSIONS" | tr -s ' ' | tr -d ' ') + # Convert comma-separated to newline-separated + NEWLINE_VERSIONS=$(echo "$VERSIONS" | tr ',' '\n') + # Validate versions + while IFS= read -r version; do + if ! [[ $version =~ ^[0-9]+(\.[0-9]+(\.[0-9]+)?)?(\.x)?$ ]]; then + echo "Error: Invalid version format: $version" + exit 1 + fi + done <<< "$NEWLINE_VERSIONS" + # Write multiline output to $GITHUB_OUTPUT + { + echo 'versions<> $GITHUB_OUTPUT - name: ⚙ dotnet if: steps.dotnet.outputs.versions != '' uses: actions/setup-dotnet@v4 with: - dotnet-version: ${{ steps.dotnet.outputs.versions }} + dotnet-version: | + ${{ steps.dotnet.outputs.versions }} diff --git a/.netconfig b/.netconfig index f9cb09c..0b47d8d 100644 --- a/.netconfig +++ b/.netconfig @@ -23,15 +23,15 @@ weak [file ".gitattributes"] url = https://github.com/devlooped/oss/blob/main/.gitattributes - sha = 5f92a68e302bae675b394ef343114139c075993e + sha = 4a9aa321c4982b83c185cf8dffed181ff84667d5 - etag = 338ba6d92c8d1774363396739c2be4257bfc58026f4b0fe92cb0ae4460e1eff7 + etag = 09cad18280ed04b67f7f87591e5481510df04d44c3403231b8af885664d8fd58 weak [file ".github/actions/dotnet/action.yml"] url = https://github.com/devlooped/oss/blob/main/.github/actions/dotnet/action.yml - sha = 08c70776943839f73dbea2e65355108747468508 + sha = f2b690ce307acb76c5b8d7faec1a5b971a93653e - etag = a5f1fa7f652cc2c2e13b7c236e5f8403aea26667d6a040c84ef976af267af6ab + etag = 27ea11baa2397b3ec9e643a935832da97719c4e44215cfd135c49cad4c29373f weak [file ".github/dependabot.yml"] url = https://github.com/devlooped/oss/blob/main/.github/dependabot.yml diff --git a/src/AI.Tests/Attributes.cs b/src/AI.Tests/Attributes.cs new file mode 100644 index 0000000..cbab72e --- /dev/null +++ b/src/AI.Tests/Attributes.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; +using Xunit; + +namespace Xunit; + +public class SecretsFactAttribute : FactAttribute +{ + public static IConfiguration Configuration { get; set; } = new ConfigurationBuilder() + .AddEnvironmentVariables() + .AddUserSecrets() + .Build(); + + public SecretsFactAttribute(params string[] secrets) + { + var missing = new HashSet(); + + foreach (var secret in secrets) + { + if (string.IsNullOrEmpty(Configuration[secret])) + missing.Add(secret); + } + + if (missing.Count > 0) + Skip = "Missing user secrets: " + string.Join(',', missing); + } +} + +public class LocalFactAttribute : SecretsFactAttribute +{ + public LocalFactAttribute(params string[] secrets) : base(secrets) + { + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI"))) + Skip = "Non-CI test"; + } +} + +public class CIFactAttribute : FactAttribute +{ + public CIFactAttribute() + { + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI"))) + Skip = "CI-only test"; + } +} + +public class SecretsTheoryAttribute : TheoryAttribute +{ + public SecretsTheoryAttribute(params string[] secrets) + { + var missing = new HashSet(); + + foreach (var secret in secrets) + { + if (string.IsNullOrEmpty(SecretsFactAttribute.Configuration[secret])) + missing.Add(secret); + } + + if (missing.Count > 0) + Skip = "Missing user secrets: " + string.Join(',', missing); + } +} + +public class LocalTheoryAttribute : SecretsTheoryAttribute +{ + public LocalTheoryAttribute(params string[] secrets) : base(secrets) + { + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI"))) + Skip = "Non-CI test"; + } +} + +public class CITheoryAttribute : SecretsTheoryAttribute +{ + public CITheoryAttribute(params string[] secrets) : base(secrets) + { + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI"))) + Skip = "CI-only test"; + } +} + +public class DebuggerFactAttribute : FactAttribute +{ + public DebuggerFactAttribute() + { + if (!System.Diagnostics.Debugger.IsAttached) + Skip = "Only running in the debugger"; + } +} + +public class DebuggerTheoryAttribute : TheoryAttribute +{ + public DebuggerTheoryAttribute() + { + if (!System.Diagnostics.Debugger.IsAttached) + Skip = "Only running in the debugger"; + } +}