diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index d86752265b..18b528665f 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,14 +3,14 @@ "isRoot": true, "tools": { "microsoft.templateengine.authoring.cli": { - "version": "10.0.100", + "version": "10.0.101", "commands": [ "dotnet-template-authoring" ], "rollForward": false }, "verify.tool": { - "version": "0.6.0", + "version": "0.7.0", "commands": [ "dotnet-verify" ], diff --git a/.github/actions/execute-pipeline/action.yml b/.github/actions/execute-pipeline/action.yml index 55e4893bc4..50c32f7793 100644 --- a/.github/actions/execute-pipeline/action.yml +++ b/.github/actions/execute-pipeline/action.yml @@ -23,7 +23,7 @@ inputs: netversion: description: 'Net version' required: false - default: 'net9.0' + default: 'net10.0' runs: using: "composite" diff --git a/.github/scripts/docs/docs/benchmarks/index.md b/.github/scripts/docs/docs/benchmarks/index.md new file mode 100644 index 0000000000..8bfca449e7 --- /dev/null +++ b/.github/scripts/docs/docs/benchmarks/index.md @@ -0,0 +1,69 @@ +--- +title: Performance Benchmarks +description: Real-world performance comparisons between TUnit and other .NET testing frameworks +sidebar_position: 1 +--- + +# Performance Benchmarks + +:::info Last Updated +These benchmarks were automatically generated on **2025-11-11** from the latest CI run. + +**Environment:** Ubuntu Latest • .NET 10 +::: + +## šŸš€ Runtime Performance + + +--- + +## šŸ“Š Methodology + +These benchmarks compare TUnit against the most popular .NET testing frameworks: + +| Framework | Version Tested | +|-----------|----------------| +| **TUnit** | latest | +| **xUnit v3** | latest | +| **NUnit** | latest | +| **MSTest** | latest | + +### Test Scenarios + +The benchmarks measure real-world testing patterns: + +- **DataDrivenTests**: Parameterized tests with multiple data sources +- **AsyncTests**: Realistic async/await patterns with I/O simulation +- **ScaleTests**: Large test suites (150+ tests) measuring scalability +- **MatrixTests**: Combinatorial test generation and execution +- **MassiveParallelTests**: Parallel execution stress tests +- **SetupTeardownTests**: Expensive test fixtures with setup/teardown overhead + +### Environment + +- **OS**: Ubuntu Latest (GitHub Actions) +- **Runtime**: .NET 10 +- **SDK**: .NET 10 SDK +- **Hardware**: GitHub Actions Standard Runner (Ubuntu) +- **Tool**: BenchmarkDotNet + +### Why These Numbers Matter + +- **No Mocking**: All tests use realistic patterns, not artificial micro-benchmarks +- **Equivalent Logic**: Each framework implements identical test scenarios +- **Warm-Up Excluded**: Measurements exclude JIT warm-up overhead +- **Statistical Rigor**: Multiple iterations with outlier detection + +### Source Code + +All benchmark source code is available in the [`tools/speed-comparison`](https://github.com/thomhurst/TUnit/tree/main/tools/speed-comparison) directory. + +--- + +:::note Continuous Benchmarking +These benchmarks run automatically daily via [GitHub Actions](https://github.com/thomhurst/TUnit/actions/workflows/speed-comparison.yml). + +Each benchmark runs multiple iterations with statistical analysis to ensure accuracy. Results may vary based on hardware and test characteristics. +::: + +*Last generated: 2025-11-11T23:48:04.871Z* diff --git a/.github/scripts/process-benchmarks.js b/.github/scripts/process-benchmarks.js index 1a58bb5282..44ebc84537 100644 --- a/.github/scripts/process-benchmarks.js +++ b/.github/scripts/process-benchmarks.js @@ -79,8 +79,10 @@ function extractEnvironmentInfo(content) { } function parseMeanValue(meanStr) { - // Parse "352.5 ms" -> 352.5 - const match = meanStr.match(/[\d.]+/); + // Parse "352.5 ms" or "1,211.6 ms" -> 352.5 or 1211.6 + // Remove commas, then extract number + const cleaned = meanStr.replace(/,/g, ''); + const match = cleaned.match(/[\d.]+/); return match ? parseFloat(match[0]) : 0; } @@ -95,6 +97,10 @@ let environmentInfo = {}; console.log('šŸ“Š Processing runtime benchmarks...'); const runtimeFiles = findMarkdownFiles(RUNTIME_DIR); console.log(` Found ${runtimeFiles.length} runtime benchmark files`); +if (runtimeFiles.length > 0) { + console.log(' Sample paths:'); + runtimeFiles.slice(0, 2).forEach(f => console.log(` ${f}`)); +} runtimeFiles.forEach(file => { const content = fs.readFileSync(file, 'utf8'); @@ -105,12 +111,17 @@ runtimeFiles.forEach(file => { } if (data) { - // Extract test category from path - const match = file.match(/run_time_([A-Za-z]+Tests)/); - const category = match ? match[1] : path.basename(path.dirname(file)); - - categories.runtime[category] = data; - console.log(` āœ“ Processed ${category}: ${data.length} frameworks`); + // Extract test category from artifact directory path + // Path structure: benchmark-results/runtime/ubuntu_markdown_run_time_/.../*.md + const match = file.match(/ubuntu_markdown_run_time_([A-Za-z]+Tests)/); + const category = match ? match[1] : null; + + if (category) { + categories.runtime[category] = data; + console.log(` āœ“ Processed ${category}: ${data.length} frameworks`); + } else { + console.warn(` āš ļø Could not extract category from file path: ${file}`); + } } }); @@ -136,168 +147,239 @@ const stats = { lastUpdated: new Date().toISOString() }; -console.log('\nšŸ“ˆ Calculating performance comparisons...'); - -function calculateComparisons() { - const comparisons = {}; - - Object.entries(categories.runtime).forEach(([category, data]) => { - const tunit = data.find(d => d.Method === 'TUnit'); - const tunitAOT = data.find(d => d.Method === 'TUnit_AOT'); - const xunit = data.find(d => d.Method === 'xUnit3'); - const nunit = data.find(d => d.Method === 'NUnit'); - const mstest = data.find(d => d.Method === 'MSTest'); - - if (tunit) { - const tunitMean = parseMeanValue(tunit.Mean); - comparisons[category] = { - tunitMean, - tunitAOTMean: tunitAOT ? parseMeanValue(tunitAOT.Mean) : null, - vsXUnit: xunit ? (parseMeanValue(xunit.Mean) / tunitMean).toFixed(2) : null, - vsNUnit: nunit ? (parseMeanValue(nunit.Mean) / tunitMean).toFixed(2) : null, - vsMSTest: mstest ? (parseMeanValue(mstest.Mean) / tunitMean).toFixed(2) : null, - aotSpeedup: tunitAOT ? (tunitMean / parseMeanValue(tunitAOT.Mean)).toFixed(2) : null - }; - } - }); - - return comparisons; -} - -const comparisons = calculateComparisons(); - -// Calculate average speedups -const avgSpeedups = { - vsXUnit: 0, - vsNUnit: 0, - vsMSTest: 0, - count: 0 -}; - -Object.values(comparisons).forEach(comp => { - if (comp.vsXUnit) { - avgSpeedups.vsXUnit += parseFloat(comp.vsXUnit); - avgSpeedups.count++; - } - if (comp.vsNUnit) avgSpeedups.vsNUnit += parseFloat(comp.vsNUnit); - if (comp.vsMSTest) avgSpeedups.vsMSTest += parseFloat(comp.vsMSTest); -}); - -if (avgSpeedups.count > 0) { - avgSpeedups.vsXUnit = (avgSpeedups.vsXUnit / avgSpeedups.count).toFixed(1); - avgSpeedups.vsNUnit = (avgSpeedups.vsNUnit / avgSpeedups.count).toFixed(1); - avgSpeedups.vsMSTest = (avgSpeedups.vsMSTest / avgSpeedups.count).toFixed(1); -} - -console.log(` Average speedup vs xUnit: ${avgSpeedups.vsXUnit}x`); -console.log(` Average speedup vs NUnit: ${avgSpeedups.vsNUnit}x`); -console.log(` Average speedup vs MSTest: ${avgSpeedups.vsMSTest}x`); - -// Generate main benchmarks page +console.log('\nšŸ“Š Preparing benchmark data...'); console.log('\nšŸ“ Generating documentation...'); const timestamp = new Date().toISOString().split('T')[0]; +const sampleData = Object.values(categories.runtime)[0] || []; +const frameworks = { + tunit: sampleData.find(d => d.Method === 'TUnit')?.Version || 'latest', + xunit: sampleData.find(d => d.Method === 'xUnit3')?.Version || 'latest', + nunit: sampleData.find(d => d.Method === 'NUnit')?.Version || 'latest', + mstest: sampleData.find(d => d.Method === 'MSTest')?.Version || 'latest' +}; -let mainPage = `--- -title: Performance Benchmarks -description: Real-world performance comparisons between TUnit and other .NET testing frameworks -sidebar_position: 1 +// Generate individual benchmark pages for each runtime category +Object.entries(categories.runtime).forEach(([testClass, data]) => { + const benchmarkPage = `--- +title: ${testClass} +description: Performance benchmark results for ${testClass} +sidebar_position: ${Object.keys(categories.runtime).indexOf(testClass) + 2} --- -# Performance Benchmarks +# ${testClass} Benchmark :::info Last Updated -These benchmarks were automatically generated on **${timestamp}** from the latest CI run. +This benchmark was automatically generated on **${timestamp}** from the latest CI run. **Environment:** ${environmentInfo.os || 'Ubuntu Latest'} • ${environmentInfo.sdk || '.NET 10'} ::: -## šŸŽÆ Executive Summary +## šŸ“Š Results + +| Framework | Version | Mean | Median | StdDev | +|-----------|---------|------|--------|--------| +${data.map(row => { + const name = row.Method.includes('TUnit_AOT') ? '**TUnit (AOT)**' : row.Method.includes('TUnit') ? '**TUnit**' : row.Method; + return `| ${name} | ${row.Version || 'N/A'} | ${row.Mean} | ${row.Median || 'N/A'} | ${row.StdDev || 'N/A'} |`; +}).join('\n')} + +## šŸ“ˆ Visual Comparison + +\`\`\`mermaid +%%{init: { + 'theme':'base', + 'themeVariables': { + 'primaryColor': '#2563eb', + 'primaryTextColor': '#ffffff', + 'primaryBorderColor': '#1e40af', + 'lineColor': '#6b7280', + 'secondaryColor': '#7c3aed', + 'tertiaryColor': '#dc2626', + 'background': '#ffffff', + 'mainBkg': '#2563eb', + 'secondBkg': '#7c3aed', + 'tertiaryBkg': '#dc2626', + 'clusterBkg': '#f3f4f6', + 'edgeLabelBackground': '#ffffff', + 'tertiaryTextColor': '#1f2937', + 'pie1': '#2563eb', + 'pie2': '#7c3aed', + 'pie3': '#dc2626', + 'pie4': '#f59e0b', + 'pie5': '#10b981', + 'pie6': '#06b6d4', + 'pie7': '#ec4899', + 'pie8': '#6366f1', + 'pie9': '#84cc16', + 'pie10': '#f97316', + 'pie11': '#14b8a6', + 'pie12': '#a855f7' + } +}}%% +xychart-beta + title "${testClass} Performance Comparison" + x-axis [${data.map(d => `"${d.Method}"`).join(', ')}] + y-axis "Time (${data[0]?.Mean.includes(' s') ? 's' : 'ms'})" 0 --> ${Math.ceil(Math.max(...data.map(d => parseMeanValue(d.Mean))) * 1.2)} + bar [${data.map(d => parseMeanValue(d.Mean)).join(', ')}] +\`\`\` -TUnit demonstrates significant performance advantages across all testing scenarios: +## šŸŽÆ Key Insights -
+This benchmark compares TUnit's performance against ${data.filter(d => !d.Method.includes('TUnit')).map(d => d.Method).join(', ')} using identical test scenarios. -### Average Performance vs Other Frameworks +--- -- **${avgSpeedups.vsXUnit}x faster** than xUnit v3 -- **${avgSpeedups.vsNUnit}x faster** than NUnit -- **${avgSpeedups.vsMSTest}x faster** than MSTest +:::note Methodology +View the [benchmarks overview](/docs/benchmarks) for methodology details and environment information. +::: -
+*Last generated: ${new Date().toISOString()}* +`; ---- + fs.writeFileSync(path.join(OUTPUT_DIR, `${testClass}.md`), benchmarkPage); + console.log(` āœ“ Created ${OUTPUT_DIR}/${testClass}.md`); + + // Generate individual JSON file for each benchmark + const benchmarkJson = { + timestamp: new Date().toISOString(), + category: testClass, + environment: environmentInfo, + results: data + }; + + fs.writeFileSync( + path.join(STATIC_DIR, `${testClass}.json`), + JSON.stringify(benchmarkJson, null, 2) + ); + console.log(` āœ“ Created ${STATIC_DIR}/${testClass}.json`); +}); -## šŸš€ Runtime Performance +// Generate build benchmark page if available +if (Object.keys(categories.build).length > 0) { + Object.entries(categories.build).forEach(([testClass, data]) => { + const benchmarkPage = `--- +title: Build Performance +description: Compilation time benchmark results +sidebar_position: ${Object.keys(categories.runtime).length + 2} +--- -`; +# Build Performance Benchmark -// Add runtime results -Object.entries(categories.runtime).forEach(([testClass, data]) => { - const comparison = comparisons[testClass]; +:::info Last Updated +This benchmark was automatically generated on **${timestamp}** from the latest CI run. - mainPage += `\n### ${testClass}\n\n`; +**Environment:** ${environmentInfo.os || 'Ubuntu Latest'} • ${environmentInfo.sdk || '.NET 10'} +::: - if (comparison && comparison.aotSpeedup) { - mainPage += `:::tip Native AOT Performance\n`; - mainPage += `TUnit with Native AOT compilation is **${comparison.aotSpeedup}x faster** than regular JIT!\n`; - mainPage += `:::\n\n`; +## šŸ“Š Results + +Compilation time comparison across frameworks: + +| Framework | Version | Mean | Median | StdDev | +|-----------|---------|------|--------|--------| +${data.map(row => { + const name = row.Method.includes('TUnit') ? '**TUnit**' : row.Method; + return `| ${name} | ${row.Version || 'N/A'} | ${row.Mean} | ${row.Median || 'N/A'} | ${row.StdDev || 'N/A'} |`; +}).join('\n')} + +## šŸ“ˆ Visual Comparison + +\`\`\`mermaid +%%{init: { + 'theme':'base', + 'themeVariables': { + 'primaryColor': '#2563eb', + 'primaryTextColor': '#ffffff', + 'primaryBorderColor': '#1e40af', + 'lineColor': '#6b7280', + 'secondaryColor': '#7c3aed', + 'tertiaryColor': '#dc2626', + 'background': '#ffffff', + 'mainBkg': '#2563eb', + 'secondBkg': '#7c3aed', + 'tertiaryBkg': '#dc2626', + 'clusterBkg': '#f3f4f6', + 'edgeLabelBackground': '#ffffff', + 'tertiaryTextColor': '#1f2937', + 'pie1': '#2563eb', + 'pie2': '#7c3aed', + 'pie3': '#dc2626', + 'pie4': '#f59e0b', + 'pie5': '#10b981', + 'pie6': '#06b6d4', + 'pie7': '#ec4899', + 'pie8': '#6366f1', + 'pie9': '#84cc16', + 'pie10': '#f97316', + 'pie11': '#14b8a6', + 'pie12': '#a855f7' } +}}%% +xychart-beta + title "Build Time Comparison" + x-axis [${data.map(d => `"${d.Method}"`).join(', ')}] + y-axis "Time (${data[0]?.Mean.includes(' s') ? 's' : 'ms'})" 0 --> ${Math.ceil(Math.max(...data.map(d => parseMeanValue(d.Mean))) * 1.2)} + bar [${data.map(d => parseMeanValue(d.Mean)).join(', ')}] +\`\`\` - // Add speedup badges - if (comparison) { - const badges = []; - if (comparison.vsXUnit) badges.push(`**${comparison.vsXUnit}x faster** than xUnit`); - if (comparison.vsNUnit) badges.push(`**${comparison.vsNUnit}x faster** than NUnit`); - if (comparison.vsMSTest) badges.push(`**${comparison.vsMSTest}x faster** than MSTest`); +--- - if (badges.length > 0) { - mainPage += `**Performance:** ${badges.join(' • ')}\n\n`; - } - } +:::note Methodology +View the [benchmarks overview](/docs/benchmarks) for methodology details and environment information. +::: - // Add table - mainPage += `| Framework | Version | Mean | Median | StdDev |\n`; - mainPage += `|-----------|---------|------|--------|--------|\n`; +*Last generated: ${new Date().toISOString()}* +`; - data.forEach(row => { - const emoji = row.Method.includes('TUnit') ? 'šŸ† ' : ''; - const name = row.Method.includes('TUnit_AOT') ? '**TUnit (AOT)**' : row.Method.includes('TUnit') ? '**TUnit**' : row.Method; - mainPage += `| ${emoji}${name} | ${row.Version || 'N/A'} | ${row.Mean} | ${row.Median || 'N/A'} | ${row.StdDev || 'N/A'} |\n`; + fs.writeFileSync(path.join(OUTPUT_DIR, 'BuildTime.md'), benchmarkPage); + console.log(` āœ“ Created ${OUTPUT_DIR}/BuildTime.md`); + + // Generate build benchmark JSON + const buildJson = { + timestamp: new Date().toISOString(), + category: 'BuildTime', + environment: environmentInfo, + results: data + }; + + fs.writeFileSync( + path.join(STATIC_DIR, 'BuildTime.json'), + JSON.stringify(buildJson, null, 2) + ); + console.log(` āœ“ Created ${STATIC_DIR}/BuildTime.json`); }); +} - mainPage += '\n'; -}); +// Generate index/overview page +const indexPage = `--- +title: Performance Benchmarks +description: Real-world performance comparisons between TUnit and other .NET testing frameworks +sidebar_position: 1 +--- -// Add build time results -if (Object.keys(categories.build).length > 0) { - mainPage += `\n---\n\n## šŸ”Ø Build Performance\n\n`; - mainPage += `Compilation time comparison across frameworks:\n\n`; +# Performance Benchmarks - Object.entries(categories.build).forEach(([testClass, data]) => { - mainPage += `| Framework | Version | Mean | Median | StdDev |\n`; - mainPage += `|-----------|---------|------|--------|--------|\n`; +:::info Last Updated +These benchmarks were automatically generated on **${timestamp}** from the latest CI run. + +**Environment:** ${environmentInfo.os || 'Ubuntu Latest'} • ${environmentInfo.sdk || '.NET 10'} +::: - data.forEach(row => { - const emoji = row.Method.includes('TUnit') ? 'šŸ† ' : ''; - const name = row.Method.includes('TUnit') ? '**TUnit**' : row.Method; - mainPage += `| ${emoji}${name} | ${row.Version || 'N/A'} | ${row.Mean} | ${row.Median || 'N/A'} | ${row.StdDev || 'N/A'} |\n`; - }); +## šŸš€ Runtime Benchmarks - mainPage += '\n'; - }); -} +Click on any benchmark to view detailed results: -// Add methodology section -const sampleData = Object.values(categories.runtime)[0] || []; -const frameworks = { - tunit: sampleData.find(d => d.Method === 'TUnit')?.Version || 'latest', - xunit: sampleData.find(d => d.Method === 'xUnit3')?.Version || 'latest', - nunit: sampleData.find(d => d.Method === 'NUnit')?.Version || 'latest', - mstest: sampleData.find(d => d.Method === 'MSTest')?.Version || 'latest' -}; +${Object.keys(categories.runtime).map(testClass => + `- [${testClass}](${testClass}) - Detailed performance analysis` +).join('\n')} + +${Object.keys(categories.build).length > 0 ? ` +## šŸ”Ø Build Benchmarks + +- [Build Performance](BuildTime) - Compilation time comparison +` : ''} -mainPage += ` --- ## šŸ“Š Methodology @@ -317,9 +399,10 @@ The benchmarks measure real-world testing patterns: - **DataDrivenTests**: Parameterized tests with multiple data sources - **AsyncTests**: Realistic async/await patterns with I/O simulation -- **ScaleTests**: Large test suites (1000+ tests) measuring scalability +- **ScaleTests**: Large test suites (150+ tests) measuring scalability - **MatrixTests**: Combinatorial test generation and execution - **MassiveParallelTests**: Parallel execution stress tests +- **SetupTeardownTests**: Expensive test fixtures with setup/teardown overhead ### Environment @@ -351,7 +434,7 @@ Each benchmark runs multiple iterations with statistical analysis to ensure accu *Last generated: ${new Date().toISOString()}* `; -fs.writeFileSync(path.join(OUTPUT_DIR, 'index.md'), mainPage); +fs.writeFileSync(path.join(OUTPUT_DIR, 'index.md'), indexPage); console.log(` āœ“ Created ${OUTPUT_DIR}/index.md`); // Generate JSON for interactive components @@ -360,8 +443,6 @@ const benchmarkData = { environment: environmentInfo, categories: categories.runtime, build: categories.build, - comparisons, - averageSpeedups: avgSpeedups, stats }; @@ -387,7 +468,6 @@ if (fs.existsSync(historicalFile)) { // Add new data point historical.push({ date: new Date().toISOString().split('T')[0], - averageSpeedups: avgSpeedups, environment: environmentInfo.os || 'Ubuntu' }); @@ -400,9 +480,38 @@ fs.writeFileSync( ); console.log(` āœ“ Updated ${historicalFile} (${historical.length} data points)`); +// Generate benchmark summary for PR body +const benchmarkSummary = { + runtime: Object.keys(categories.runtime), + build: Object.keys(categories.build), + timestamp: timestamp, + environment: `${environmentInfo.os || 'Ubuntu Latest'} • ${environmentInfo.sdk || '.NET 10'}` +}; + +fs.writeFileSync( + path.join(STATIC_DIR, 'summary.json'), + JSON.stringify(benchmarkSummary, null, 2) +); +console.log(` āœ“ Created ${STATIC_DIR}/summary.json`); + console.log('\nāœ… Benchmark processing complete!\n'); console.log(`Summary:`); console.log(` - Runtime categories: ${stats.runtimeCategories}`); console.log(` - Build categories: ${stats.buildCategories}`); console.log(` - Total benchmarks: ${stats.totalBenchmarks}`); -console.log(` - Output files: 3 (markdown + 2 JSON files)`); +console.log(` - Markdown pages generated: ${stats.runtimeCategories + stats.buildCategories + 1}`); +console.log(` - JSON files generated: ${stats.runtimeCategories + stats.buildCategories + 3}`); +console.log(`\nšŸ“Š Benchmarks produced:`); +console.log(`\nRuntime Benchmarks (${Object.keys(categories.runtime).length}):`); +Object.keys(categories.runtime).forEach(cat => console.log(` - ${cat}`)); +if (Object.keys(categories.build).length > 0) { + console.log(`\nBuild Benchmarks (${Object.keys(categories.build).length}):`); + Object.keys(categories.build).forEach(cat => console.log(` - ${cat}`)); +} + +// Validation warning +if (Object.keys(categories.runtime).length === 0) { + console.warn('\nāš ļø WARNING: No runtime benchmark categories were found!'); + console.warn('This likely means the artifact directory structure is not as expected.'); + console.warn('Expected structure: benchmark-results/runtime/ubuntu_markdown_run_time_/'); +} diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index dc3652b5b1..bbe61036fe 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -9,6 +9,12 @@ on: # - "src/**/*.tsx" # - "src/**/*.js" # - "src/**/*.jsx" + workflow_dispatch: + inputs: + pr_number: + description: 'PR number to review' + required: true + type: number jobs: claude-review: @@ -17,62 +23,58 @@ jobs: # github.event.pull_request.user.login == 'external-contributor' || # github.event.pull_request.user.login == 'new-developer' || # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' - + runs-on: ubuntu-latest permissions: contents: read pull-requests: read issues: read id-token: write - + steps: - - name: Checkout repository - uses: actions/checkout@v5 + - name: Get PR info (manual trigger) + if: github.event_name == 'workflow_dispatch' + id: pr-info + uses: actions/github-script@v8 + with: + script: | + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: ${{ inputs.pr_number }} + }); + core.setOutput('ref', pr.head.ref); + core.setOutput('repo', pr.head.repo.full_name); + return pr; + + - name: Checkout PR branch + uses: actions/checkout@v6 with: - fetch-depth: 1 + ref: ${{ github.event_name == 'workflow_dispatch' && steps.pr-info.outputs.ref || github.event.pull_request.head.ref }} + repository: ${{ github.event_name == 'workflow_dispatch' && steps.pr-info.outputs.repo || github.event.pull_request.head.repo.full_name }} + fetch-depth: 0 - name: Run Claude Code Review id: claude-review - uses: anthropics/claude-code-action@beta + uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + prompt: | + REPO: ${{ github.repository }} + PR NUMBER: ${{ inputs.pr_number || github.event.pull_request.number }} - # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4) - # model: "claude-opus-4-20250514" - - # Direct prompt for automated review (no @claude mention needed) - direct_prompt: | Please review this pull request and provide feedback on: - Code quality and best practices - Potential bugs or issues - Performance considerations - Security concerns - Test coverage - - Be constructive and helpful in your feedback. - # Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR - # use_sticky_comment: true - - # Optional: Customize review based on file types - # direct_prompt: | - # Review this PR focusing on: - # - For TypeScript files: Type safety and proper interface usage - # - For API endpoints: Security, input validation, and error handling - # - For React components: Performance, accessibility, and best practices - # - For tests: Coverage, edge cases, and test quality - - # Optional: Different prompts for different authors - # direct_prompt: | - # ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' && - # 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' || - # 'Please provide a thorough code review focusing on our coding standards and best practices.' }} - - # Optional: Add specific tools for running tests or linting - # allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)" - - # Optional: Skip review for certain conditions - # if: | - # !contains(github.event.pull_request.title, '[skip-review]') && - # !contains(github.event.pull_request.title, '[WIP]') + Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback. + + Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR. + + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"' diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 6de0bdfc3e..94dcce5581 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -26,39 +26,25 @@ jobs: actions: read # Required for Claude to read CI results on PRs steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1 - name: Run Claude Code id: claude - uses: anthropics/claude-code-action@beta + uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # This is an optional setting that allows Claude to read CI results on PRs additional_permissions: | actions: read - - # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4) - # model: "claude-opus-4-20250514" - - # Optional: Customize the trigger phrase (default: @claude) - # trigger_phrase: "/claude" - - # Optional: Trigger when specific user is assigned to an issue - # assignee_trigger: "claude-bot" - - # Optional: Allow Claude to run specific commands - allowed_tools: "Bash(pwsh run-all-engine-tests.ps1)" - - # Optional: Add custom instructions for Claude to customize its behavior for your project - # custom_instructions: | - # Follow our coding standards - # Ensure all new code has tests - # Use TypeScript for new files - - # Optional: Custom environment variables for Claude - # claude_env: | - # NODE_ENV: test + + # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. + # prompt: 'Update the pull request description to include a summary of changes.' + + # Optional: Add claude_args to customize behavior and configuration + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + # claude_args: '--allowed-tools Bash(gh pr:*)' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 4dde9743e2..3d2840bc6d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -55,7 +55,7 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup .NET 9 uses: actions/setup-dotnet@v5 diff --git a/.github/workflows/deploy-pages-test.yml b/.github/workflows/deploy-pages-test.yml index 261a136ea7..f7dddc272e 100644 --- a/.github/workflows/deploy-pages-test.yml +++ b/.github/workflows/deploy-pages-test.yml @@ -13,7 +13,7 @@ jobs: run: working-directory: docs steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: actions/setup-node@v6 diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index e41288cdef..2040177ff8 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -16,7 +16,7 @@ jobs: run: working-directory: docs steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: actions/setup-node@v6 diff --git a/.github/workflows/dotnet-build-different-locale.yml b/.github/workflows/dotnet-build-different-locale.yml index fcb846d76d..21309e9d00 100644 --- a/.github/workflows/dotnet-build-different-locale.yml +++ b/.github/workflows/dotnet-build-different-locale.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 60785d933d..628377cef7 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -26,7 +26,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 @@ -66,7 +66,7 @@ jobs: - name: Docker Setup Docker if: matrix.os == 'ubuntu-latest' - uses: docker/setup-docker-action@v4.5.0 + uses: docker/setup-docker-action@v4.6.0 - name: Cache Playwright Browsers uses: actions/cache@v4 diff --git a/.github/workflows/generate-readme.yml b/.github/workflows/generate-readme.yml index 9b06bcf623..f7a36d093f 100644 --- a/.github/workflows/generate-readme.yml +++ b/.github/workflows/generate-readme.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/speed-comparison.yml b/.github/workflows/speed-comparison.yml index 316ddff2d3..cd8f8a4387 100644 --- a/.github/workflows/speed-comparison.yml +++ b/.github/workflows/speed-comparison.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 @@ -45,7 +45,7 @@ jobs: environment: ${{ github.ref == 'refs/heads/main' && 'Production' || 'Pull Requests' }} strategy: matrix: - class: [DataDrivenTests, AsyncTests, ScaleTests, MatrixTests, MassiveParallelTests] + class: [DataDrivenTests, AsyncTests, ScaleTests, MatrixTests, MassiveParallelTests, SetupTeardownTests] fail-fast: false runs-on: ubuntu-latest concurrency: @@ -53,7 +53,7 @@ jobs: cancel-in-progress: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 @@ -97,7 +97,7 @@ jobs: cancel-in-progress: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 @@ -123,7 +123,7 @@ jobs: path: | **/BenchmarkDotNet.Artifacts/** - aggregate-and-commit-results: + process-and-upload-benchmarks: needs: [run-time-benchmarks, build-time-benchmarks] runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' @@ -132,24 +132,24 @@ jobs: pull-requests: write steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.ADMIN_TOKEN }} - name: Download All Runtime Benchmark Artifacts uses: actions/download-artifact@v6 with: path: benchmark-results/runtime/ pattern: ubuntu_markdown_run_time_* - merge-multiple: true + merge-multiple: false - name: Download Build Time Benchmark Artifacts uses: actions/download-artifact@v6 with: path: benchmark-results/build/ pattern: ubuntu_markdown_build_time - merge-multiple: true + merge-multiple: false - name: Setup Node.js uses: actions/setup-node@v6 @@ -160,6 +160,109 @@ jobs: run: | node .github/scripts/process-benchmarks.js + - name: Upload Individual Runtime Benchmarks + uses: actions/upload-artifact@v5 + if: always() + with: + name: benchmark-DataDrivenTests + path: | + docs/docs/benchmarks/DataDrivenTests.md + docs/static/benchmarks/DataDrivenTests.json + retention-days: 90 + + - name: Upload Individual Runtime Benchmarks + uses: actions/upload-artifact@v5 + if: always() + with: + name: benchmark-AsyncTests + path: | + docs/docs/benchmarks/AsyncTests.md + docs/static/benchmarks/AsyncTests.json + retention-days: 90 + + - name: Upload Individual Runtime Benchmarks + uses: actions/upload-artifact@v5 + if: always() + with: + name: benchmark-ScaleTests + path: | + docs/docs/benchmarks/ScaleTests.md + docs/static/benchmarks/ScaleTests.json + retention-days: 90 + + - name: Upload Individual Runtime Benchmarks + uses: actions/upload-artifact@v5 + if: always() + with: + name: benchmark-MatrixTests + path: | + docs/docs/benchmarks/MatrixTests.md + docs/static/benchmarks/MatrixTests.json + retention-days: 90 + + - name: Upload Individual Runtime Benchmarks + uses: actions/upload-artifact@v5 + if: always() + with: + name: benchmark-MassiveParallelTests + path: | + docs/docs/benchmarks/MassiveParallelTests.md + docs/static/benchmarks/MassiveParallelTests.json + retention-days: 90 + + - name: Upload Individual Runtime Benchmarks + uses: actions/upload-artifact@v5 + if: always() + with: + name: benchmark-SetupTeardownTests + path: | + docs/docs/benchmarks/SetupTeardownTests.md + docs/static/benchmarks/SetupTeardownTests.json + retention-days: 90 + + - name: Upload Build Benchmark + uses: actions/upload-artifact@v5 + if: always() + with: + name: benchmark-BuildTime + path: | + docs/docs/benchmarks/BuildTime.md + docs/static/benchmarks/BuildTime.json + retention-days: 90 + + - name: Upload Summary Files + uses: actions/upload-artifact@v5 + if: always() + with: + name: benchmark-summary + path: | + docs/docs/benchmarks/index.md + docs/static/benchmarks/latest.json + docs/static/benchmarks/summary.json + docs/static/benchmarks/historical.json + retention-days: 90 + + - name: Generate Benchmark List + id: benchmark_list + run: | + # Read the summary.json file and format it for the PR body + SUMMARY=$(cat docs/static/benchmarks/summary.json) + RUNTIME_BENCHMARKS=$(echo "$SUMMARY" | grep -A 100 '"runtime"' | grep -o '"[^"]*Tests"' | sed 's/"//g' | sed 's/^/- /' | tr '\n' '|') + BUILD_BENCHMARKS=$(echo "$SUMMARY" | grep -A 100 '"build"' | grep -o '"[^"]*"' | sed 's/"//g' | grep -v 'runtime\|build\|timestamp\|environment' | sed 's/^/- /' | tr '\n' '|') + + # Replace | with newlines for proper formatting + RUNTIME_LIST=$(echo "$RUNTIME_BENCHMARKS" | sed 's/|/\n/g') + BUILD_LIST=$(echo "$BUILD_BENCHMARKS" | sed 's/|/\n/g') + + # Set output with proper escaping + echo "runtime_benchmarks<> $GITHUB_OUTPUT + echo "$RUNTIME_LIST" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + echo "build_benchmarks<> $GITHUB_OUTPUT + echo "$BUILD_LIST" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + - name: Check for Changes id: check_changes run: | @@ -175,9 +278,9 @@ jobs: - name: Create Pull Request if: steps.check_changes.outputs.has_changes == 'true' id: create_pr - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@v8 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.ADMIN_TOKEN }} commit-message: 'chore: update benchmark results' branch: automated-benchmarks-update delete-branch: true @@ -187,10 +290,29 @@ jobs: This PR updates the benchmark documentation with the latest results from the Speed Comparison workflow. + ### Benchmarks Produced + + Individual benchmark artifacts are available for download: + - `benchmark-DataDrivenTests` + - `benchmark-AsyncTests` + - `benchmark-ScaleTests` + - `benchmark-MatrixTests` + - `benchmark-MassiveParallelTests` + - `benchmark-SetupTeardownTests` + - `benchmark-BuildTime` + - `benchmark-summary` (aggregated overview) + + #### Runtime Benchmarks + ${{ steps.benchmark_list.outputs.runtime_benchmarks }} + + #### Build Benchmarks + ${{ steps.benchmark_list.outputs.build_benchmarks }} + ### Changes - Updated benchmark data in `docs/static/benchmarks/latest.json` - Updated historical trends in `docs/static/benchmarks/historical.json` - Regenerated benchmark documentation in `docs/docs/benchmarks/index.md` + - Updated benchmark summary in `docs/static/benchmarks/summary.json` ### Workflow Run - **Run ID**: ${{ github.run_id }} @@ -204,12 +326,15 @@ jobs: automated benchmarks documentation + ignore-for-release draft: false - - name: Enable Auto-Merge + - name: Merge PR Immediately if: steps.check_changes.outputs.has_changes == 'true' && steps.create_pr.outputs.pull-request-number != '' env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.ADMIN_TOKEN }} run: | - gh pr merge ${{ steps.create_pr.outputs.pull-request-number }} --auto --squash --delete-branch + # Wait a moment for PR to be fully created + sleep 5 + gh pr merge ${{ steps.create_pr.outputs.pull-request-number }} --squash --delete-branch --admin diff --git a/.idea/.idea.TUnit/.idea/copilot.data.migration.ask.xml b/.idea/.idea.TUnit/.idea/copilot.data.migration.ask.xml deleted file mode 100644 index 7ef04e2ea0..0000000000 --- a/.idea/.idea.TUnit/.idea/copilot.data.migration.ask.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/.idea.TUnit/.idea/copilot.data.migration.ask2agent.xml b/.idea/.idea.TUnit/.idea/copilot.data.migration.ask2agent.xml deleted file mode 100644 index 1f2ea11e7f..0000000000 --- a/.idea/.idea.TUnit/.idea/copilot.data.migration.ask2agent.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/.idea.TUnit/.idea/indexLayout.xml b/.idea/.idea.TUnit/.idea/indexLayout.xml deleted file mode 100644 index 7b08163ceb..0000000000 --- a/.idea/.idea.TUnit/.idea/indexLayout.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.serena/.gitignore b/.serena/.gitignore deleted file mode 100644 index 14d86ad623..0000000000 --- a/.serena/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/cache diff --git a/.serena/cache/csharp/document_symbols_cache_v20-05-25.pkl b/.serena/cache/csharp/document_symbols_cache_v20-05-25.pkl deleted file mode 100644 index 091ba63e6d..0000000000 Binary files a/.serena/cache/csharp/document_symbols_cache_v20-05-25.pkl and /dev/null differ diff --git a/.serena/cache/csharp/document_symbols_cache_v23-06-25.pkl b/.serena/cache/csharp/document_symbols_cache_v23-06-25.pkl deleted file mode 100644 index 1116471997..0000000000 Binary files a/.serena/cache/csharp/document_symbols_cache_v23-06-25.pkl and /dev/null differ diff --git a/.serena/memories/code_style_conventions.md b/.serena/memories/code_style_conventions.md deleted file mode 100644 index f8715af5f1..0000000000 --- a/.serena/memories/code_style_conventions.md +++ /dev/null @@ -1,30 +0,0 @@ -# TUnit Code Style and Conventions - -## General Guidelines -- **Self-Descriptive Code**: Write self-descriptive code instead of adding redundant comments -- **Clean Architecture**: Maintain separation between source generation (data only) and runtime (logic) -- **Async Best Practices**: Never use `.Result`, `.Wait()`, or `GetAwaiter().GetResult()` - always use proper async/await - -## Async Guidelines (Critical) -- **Never Block on Async**: Avoid `GetAwaiter().GetResult()`, `.Result`, or `.Wait()` to prevent deadlocks -- **Async All the Way**: When in sync context needing async, refactor method signatures to be async -- **Embrace Async**: TUnit supports async throughout the stack - use it properly -- **Proper Cancellation**: Use CancellationToken properly for long-running operations - -## Architecture Patterns -- **Source Generators**: Should only emit data structures (TestMetadata), never execution logic -- **Runtime Phase**: TestBuilder handles complex logic (data expansion, tuple unwrapping, etc.) -- **Performance Focus**: Use expression compilation and caching in TestBuilder -- **Extension Points**: Support custom executors, data sources, hooks, and assertions - -## Project Structure -- Core logic in `TUnit.Core` -- Source generation in `TUnit.Core.SourceGenerator` -- Test execution in `TUnit.Engine` -- Assertions in `TUnit.Assertions` -- Tests in `TUnit.UnitTests` (framework) and `TUnit.TestProject` (integration) - -## Testing Conventions -- Use TUnit for framework testing -- Add analyzer rules when adding new features -- Ensure Microsoft.Testing.Platform compatibility \ No newline at end of file diff --git a/.serena/memories/project_overview.md b/.serena/memories/project_overview.md deleted file mode 100644 index 01500708de..0000000000 --- a/.serena/memories/project_overview.md +++ /dev/null @@ -1,31 +0,0 @@ -# TUnit Project Overview - -## Purpose -TUnit is a modern, source-generated testing framework (with a reflection-based fallback) for .NET that provides: -- Compile-time test discovery through source generators -- Parallel test execution by default -- Native AOT and trimming support -- Built on Microsoft.Testing.Platform -- Fluent assertion library -- Rich extensibility model - -## Tech Stack -- **Language**: C# -- **Target Frameworks**: .NET 8.0 and .NET 9.0 -- **SDK Requirement**: .NET SDK 9.0.301 or later (see global.json) -- **Architecture**: Source generation + runtime execution engine -- **Testing Platform**: Microsoft.Testing.Platform integration - -## Core Components -1. **TUnit.Core**: Core abstractions and attributes -2. **TUnit.Core.SourceGenerator**: Source generators for compile-time test discovery -3. **TUnit.Engine**: Test execution engine with simplified architecture -4. **TUnit.Assertions**: Fluent assertion library with async support -5. **Extension Projects**: Playwright, F#, Analyzers, Templates - -## Key Features -- **Clean Architecture**: Separation between source generation (data) and runtime (logic) -- **Parallel-First**: Tests run in parallel by default with smart scheduling -- **Async Support**: All public APIs support async operations -- **Extensible**: Custom executors, data sources, hooks, and assertions -- **Dual Mode**: TUnit supports Source Generated mode and Reflection Mode, and both should maintain feature parity, with the exception of the source generated code path MUST support Native AOT and trimming diff --git a/.serena/memories/suggested_commands.md b/.serena/memories/suggested_commands.md deleted file mode 100644 index fc9781b920..0000000000 --- a/.serena/memories/suggested_commands.md +++ /dev/null @@ -1,52 +0,0 @@ -# Suggested Commands for TUnit Development - -## Build Commands -```bash -# Debug build -dotnet build - -# Release build -dotnet build -c Release - -# Clean build artifacts -./clean.ps1 - -# Build NuGet packages -dotnet pack -c Release -``` - -## Test Execution Commands -```bash -# Run tests in a project (3 equivalent ways) -cd [TestProjectDirectory] -dotnet run -c Release -dotnet test -c Release -dotnet exec bin/Release/net8.0/TestProject.dll - -# Test options -dotnet run -- --list-tests # List all tests -dotnet run -- --fail-fast # Stop on first failure -dotnet run -- --maximum-parallel-tests 10 # Control parallelism -dotnet run -- --report-trx --coverage # Generate reports -dotnet run -- --treenode-filter "/*/*/*/TestName" # Run specific test by exact name -dotnet run -- --treenode-filter/*/*/*PartialName*/* # Filter tests by partial name pattern -``` - -## Development Commands -```bash -# Run full pipeline -dotnet run --project TUnit.Pipeline/TUnit.Pipeline.csproj - -# Documentation site -cd docs -npm install # First time only -npm start # Run locally at localhost:3000 -npm run build # Build static site -``` - -## System Commands (Linux) -- `ls` - List directory contents -- `find` - Search for files/directories -- `grep` - Search text in files -- `git` - Version control operations -- `cd` - Change directory diff --git a/.serena/memories/task_completion_checklist.md b/.serena/memories/task_completion_checklist.md deleted file mode 100644 index 99dcdb05be..0000000000 --- a/.serena/memories/task_completion_checklist.md +++ /dev/null @@ -1,41 +0,0 @@ -# Task Completion Checklist - -When completing a coding task in TUnit: - -## Code Quality -- [ ] Follow async best practices (no `.Result`, `.Wait()`, `GetAwaiter().GetResult()`) -- [ ] Write self-descriptive code without redundant comments -- [ ] Maintain clean architecture separation (source gen vs runtime) -- [ ] Use proper error handling and logging -- [ ] Do not over-engineer -- [ ] Follow C# best practices, as well as principles such as DRY, KISS, SRP and SOLID - -## Testing -- [ ] Run relevant tests: `dotnet run -c Release` in test project directory -- [ ] Add/update unit tests in `TUnit.UnitTests` for framework changes -- [ ] Add/update integration tests in `TUnit.TestProject` for end-to-end scenarios and add the [EngineTest(ExpectedResult.Pass)] attribute so the pipeline knows to run these tests and they must pass -- [ ] Verify no hanging processes or infinite loops - -## Architecture Compliance -- [ ] Source generators only emit data, not execution logic -- [ ] Runtime components handle all complex logic -- [ ] Maintain Microsoft.Testing.Platform compatibility -- [ ] Ensure proper async support throughout -- [ ] Ensure feature parity between source generation mode and reflection mode - -## Performance & Reliability -- [ ] Check for potential deadlocks or blocking operations -- [ ] Verify proper resource disposal -- [ ] Test cancellation token handling -- [ ] Validate timeout mechanisms work correctly -- [ ] Ensure code is performant - -## Documentation (if applicable) -- [ ] Update relevant documentation in `docs/` if behavior changes -- [ ] If building a new feature, add new documentation in `docs/` in the relevant location with clear, easy to read language and code examples -- [ ] Update CLAUDE.md if architectural patterns change - -## Final Verification -- [ ] Build solution: `dotnet build -c Release` -- [ ] Run full test suite -- [ ] Check for memory leaks or hanging processes diff --git a/.serena/project.yml b/.serena/project.yml deleted file mode 100644 index 5a63a94911..0000000000 --- a/.serena/project.yml +++ /dev/null @@ -1,66 +0,0 @@ -# language of the project (csharp, python, rust, java, typescript, javascript, go, cpp, or ruby) -# Special requirements: -# * csharp: Requires the presence of a .sln file in the project folder. -language: csharp - -# whether to use the project's gitignore file to ignore files -# Added on 2025-04-07 -ignore_all_files_in_gitignore: true -# list of additional paths to ignore -# same syntax as gitignore, so you can use * and ** -# Was previously called `ignored_dirs`, please update your config if you are using that. -# Added (renamed)on 2025-04-07 -ignored_paths: [] - -# whether the project is in read-only mode -# If set to true, all editing tools will be disabled and attempts to use them will result in an error -# Added on 2025-04-18 -read_only: false - - -# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details. -# Below is the complete list of tools for convenience. -# To make sure you have the latest list of tools, and to view their descriptions, -# execute `uv run scripts/print_tool_overview.py`. -# -# * `activate_project`: Activates a project by name. -# * `check_onboarding_performed`: Checks whether project onboarding was already performed. -# * `create_text_file`: Creates/overwrites a file in the project directory. -# * `delete_lines`: Deletes a range of lines within a file. -# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. -# * `execute_shell_command`: Executes a shell command. -# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. -# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). -# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). -# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. -# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file or directory. -# * `initial_instructions`: Gets the initial instructions for the current project. -# Should only be used in settings where the system prompt cannot be set, -# e.g. in clients you have no control over, like Claude Desktop. -# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. -# * `insert_at_line`: Inserts content at a given line in a file. -# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. -# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). -# * `list_memories`: Lists memories in Serena's project-specific memory store. -# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). -# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). -# * `read_file`: Reads a file within the project directory. -# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. -# * `remove_project`: Removes a project from the Serena configuration. -# * `replace_lines`: Replaces a range of lines within a file with new content. -# * `replace_symbol_body`: Replaces the full definition of a symbol. -# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. -# * `search_for_pattern`: Performs a search for a pattern in the project. -# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. -# * `switch_modes`: Activates modes by providing a list of their names -# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. -# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. -# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. -# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. -excluded_tools: [] - -# initial prompt for the project. It will always be given to the LLM upon activating the project -# (contrary to the memories, which are loaded on demand). -initial_prompt: "" - -project_name: "TUnit" diff --git a/CLAUDE.md b/CLAUDE.md index 1ed9227dd1..925f3571f6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -147,6 +147,17 @@ dotnet test TUnit.Core.SourceGenerator.Tests **Rule**: Only run TUnit.TestProject with explicit `--treenode-filter` to target specific tests or classes. +**IMPORTANT: Run filters ONE AT A TIME!** Using OR patterns (`Pattern1|Pattern2`) can match thousands of unintended tests. Always run one specific filter per command: + +```bash +# āŒ WRONG - OR patterns can match too broadly +--treenode-filter "/*/*/ClassA/*|/*/*/ClassB/*" + +# āœ… CORRECT - Run separate commands for each class +dotnet run -- --treenode-filter "/*/*/ClassA/*" +dotnet run -- --treenode-filter "/*/*/ClassB/*" +``` + --- ### Most Common Commands diff --git a/Directory.Build.props b/Directory.Build.props index a351596691..3a10242347 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -69,4 +69,16 @@ - + + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index d5240f97bb..4176efb6f3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,20 +7,20 @@ - - + + - + - - - + + + - - - + + + - + @@ -32,18 +32,18 @@ - - + + - - + + - + @@ -52,15 +52,15 @@ - + - + - - - + + + @@ -73,26 +73,26 @@ - + - - - + + + - - - - - - - + + + + + + + - - - + + + \ No newline at end of file diff --git a/Polyfill.targets b/Polyfill.targets index 27f0006759..ca88149295 100644 --- a/Polyfill.targets +++ b/Polyfill.targets @@ -1,14 +1,6 @@ - - true - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/Roslyn.props b/Roslyn.props index 24cdff7c9e..9a3940c423 100644 --- a/Roslyn.props +++ b/Roslyn.props @@ -1,16 +1,9 @@ - - - true - + - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.cs b/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.cs index 3683793e41..e28299d735 100644 --- a/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.cs +++ b/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.cs @@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis.CSharp.Testing; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Text; namespace TUnit.Analyzers.Tests.Verifiers; @@ -16,6 +17,12 @@ public class Test : CSharpCodeFixTest { var project = solution.GetProject(projectId); diff --git a/TUnit.Analyzers.Tests/Verifiers/LineEndingNormalizingVerifier.cs b/TUnit.Analyzers.Tests/Verifiers/LineEndingNormalizingVerifier.cs index e12d6722ca..cecbb774c4 100644 --- a/TUnit.Analyzers.Tests/Verifiers/LineEndingNormalizingVerifier.cs +++ b/TUnit.Analyzers.Tests/Verifiers/LineEndingNormalizingVerifier.cs @@ -60,7 +60,17 @@ public void NotEmpty(string collectionName, IEnumerable collection) public void SequenceEqual(IEnumerable expected, IEnumerable actual, IEqualityComparer? equalityComparer = null, string? message = null) { - _defaultVerifier.SequenceEqual(expected, actual, equalityComparer, message); + // Normalize line endings for string sequence comparisons + if (typeof(T) == typeof(string)) + { + var normalizedExpected = expected.Cast().Select(NormalizeLineEndings).Cast(); + var normalizedActual = actual.Cast().Select(NormalizeLineEndings).Cast(); + _defaultVerifier.SequenceEqual(normalizedExpected, normalizedActual, equalityComparer, message); + } + else + { + _defaultVerifier.SequenceEqual(expected, actual, equalityComparer, message); + } } public IVerifier PushContext(string context) diff --git a/TUnit.Analyzers/AnalyzerReleases.Shipped.md b/TUnit.Analyzers/AnalyzerReleases.Shipped.md index 3985959552..7438273863 100644 --- a/TUnit.Analyzers/AnalyzerReleases.Shipped.md +++ b/TUnit.Analyzers/AnalyzerReleases.Shipped.md @@ -28,7 +28,7 @@ TUnit0038 | Usage | Error | Property with data attribute must have a data source TUnit0043 | Usage | Error | Properties with data attributes must use 'required' keyword TUnit0044 | Usage | Error | Properties with data attributes must have a setter TUnit0045 | Usage | Error | Property has multiple data source attributes - use only one -TUnit0046 | Usage | Warning | Data source should return Func for lazy evaluation instead of T +TUnit0046 | Usage | Warning | Data source should return Func for reference types to ensure test isolation TUnit0049 | Usage | Error | [Matrix] parameters require [MatrixDataSource] attribute on the test method TUnit0050 | Usage | Error | Too many test arguments provided TUnit0056 | Usage | Error | Instance data source methods must use [InstanceMethodDataSource] attribute diff --git a/TUnit.Analyzers/Resources.resx b/TUnit.Analyzers/Resources.resx index b68d6eadff..4482dd6a1e 100644 --- a/TUnit.Analyzers/Resources.resx +++ b/TUnit.Analyzers/Resources.resx @@ -328,13 +328,13 @@ Too many data attributes - Return a `Func<T>` rather than a `<T>`. + When a data source method provides reference types (other than string) as test parameters, it should return Func<T> to defer object creation until the test runs. This prevents shared state between tests and ensures proper test isolation. - Return a `Func<T>` rather than a `<T>`. + Data source method should return Func<T> for reference type parameters (other than string) to ensure proper test isolation - Return a `Func<T>` rather than a `<T>` + Data source should return Func<T> for reference types For AsyncLocal values set in before hooks, you must call `context.AddAsyncLocalValues` to access them within tests. diff --git a/TUnit.Assertions.FSharp/TUnit.Assertions.FSharp.fsproj b/TUnit.Assertions.FSharp/TUnit.Assertions.FSharp.fsproj index 7eeceee31c..2c94708a04 100644 --- a/TUnit.Assertions.FSharp/TUnit.Assertions.FSharp.fsproj +++ b/TUnit.Assertions.FSharp/TUnit.Assertions.FSharp.fsproj @@ -6,6 +6,7 @@ + diff --git a/TUnit.Assertions.FSharp/TaskAssert.fs b/TUnit.Assertions.FSharp/TaskAssert.fs new file mode 100644 index 0000000000..3689e47d70 --- /dev/null +++ b/TUnit.Assertions.FSharp/TaskAssert.fs @@ -0,0 +1,15 @@ +module TUnit.Assertions.FSharp.TaskAssert + +open TUnit.Assertions.Core + +[] +module TaskAssertBuilder = + let taskAssert = task + +[] +module TaskAssertCEExtensions = + type TaskBuilderBase with + #nowarn "FS1204" + member inline x.Bind(assertion: IAssertion, continuation: Unit -> TaskCode<'TOverall, 'TResult2>) : TaskCode<'TOverall, 'TResult2> = + let task = assertion.AssertAsync() + x.Bind(task, continuation) diff --git a/TUnit.Assertions.SourceGenerator.Tests/TestsBase.cs b/TUnit.Assertions.SourceGenerator.Tests/TestsBase.cs index eddc6c2fda..10df29a364 100644 --- a/TUnit.Assertions.SourceGenerator.Tests/TestsBase.cs +++ b/TUnit.Assertions.SourceGenerator.Tests/TestsBase.cs @@ -37,7 +37,7 @@ public Task RunTest(string inputFile, Func assertions) public async Task RunTest(string inputFile, RunTestOptions runTestOptions, Func assertions) { #if NET - var source = await FilePolyfill.ReadAllTextAsync(inputFile); + var source = await File.ReadAllTextAsync(inputFile); #else var source = File.ReadAllText(inputFile); #endif @@ -80,7 +80,7 @@ namespace System.Diagnostics.CodeAnalysis; public class UnconditionalSuppressMessageAttribute : Attribute; """, #if NET - ..await Task.WhenAll(runTestOptions.AdditionalFiles.Select(x => FilePolyfill.ReadAllTextAsync(x))), + ..await Task.WhenAll(runTestOptions.AdditionalFiles.Select(x => File.ReadAllTextAsync(x))), #else ..runTestOptions.AdditionalFiles.Select(x => File.ReadAllText(x)), #endif @@ -160,8 +160,8 @@ Have you added required references and additional files? verifyTask = verifyTask.OnVerifyMismatch(async (pair, message, verify) => { - var received = await FilePolyfill.ReadAllTextAsync(pair.ReceivedPath); - var verified = await FilePolyfill.ReadAllTextAsync(pair.VerifiedPath); + var received = await File.ReadAllTextAsync(pair.ReceivedPath); + var verified = await File.ReadAllTextAsync(pair.VerifiedPath); // Better diff message since original one is too large await Assert.That(Scrub(received)).IsEqualTo(Scrub(verified)); diff --git a/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs b/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs index 8cb0b1debb..1621a2f664 100644 --- a/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs +++ b/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs @@ -226,6 +226,22 @@ private static void GenerateExtensionMethod( // Skip the first parameter (AssertionContext) var additionalParams = constructor.Parameters.Skip(1).ToArray(); + // Check for RequiresUnreferencedCode attribute on the constructor first, then fall back to class-level + var constructorRequiresUnreferencedCodeAttr = constructor.GetAttributes() + .FirstOrDefault(attr => attr.AttributeClass?.Name == "RequiresUnreferencedCodeAttribute"); + + string? requiresUnreferencedCodeMessage = null; + if (constructorRequiresUnreferencedCodeAttr != null && constructorRequiresUnreferencedCodeAttr.ConstructorArguments.Length > 0) + { + // Constructor-level attribute takes precedence + requiresUnreferencedCodeMessage = constructorRequiresUnreferencedCodeAttr.ConstructorArguments[0].Value?.ToString(); + } + else if (!string.IsNullOrEmpty(data.RequiresUnreferencedCodeMessage)) + { + // Fall back to class-level attribute + requiresUnreferencedCodeMessage = data.RequiresUnreferencedCodeMessage; + } + // Build generic type parameters string // Use the assertion class's own type parameters if it has them var genericParams = new List(); @@ -315,10 +331,10 @@ private static void GenerateExtensionMethod( sourceBuilder.AppendLine($" /// Extension method for {assertionType.Name}."); sourceBuilder.AppendLine(" /// "); - // Add RequiresUnreferencedCode attribute if present - if (!string.IsNullOrEmpty(data.RequiresUnreferencedCodeMessage)) + // Add RequiresUnreferencedCode attribute if present (from constructor or class level) + if (!string.IsNullOrEmpty(requiresUnreferencedCodeMessage)) { - var escapedMessage = data.RequiresUnreferencedCodeMessage!.Replace("\"", "\\\""); + var escapedMessage = requiresUnreferencedCodeMessage!.Replace("\"", "\\\""); sourceBuilder.AppendLine($" [global::System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(\"{escapedMessage}\")]"); } diff --git a/TUnit.Assertions.Tests/AssertConditions/BecauseTests.cs b/TUnit.Assertions.Tests/AssertConditions/BecauseTests.cs index 1d44b17e48..0c4c88137f 100644 --- a/TUnit.Assertions.Tests/AssertConditions/BecauseTests.cs +++ b/TUnit.Assertions.Tests/AssertConditions/BecauseTests.cs @@ -1,4 +1,4 @@ -namespace TUnit.Assertions.Tests.AssertConditions; +namespace TUnit.Assertions.Tests.AssertConditions; public class BecauseTests { @@ -68,7 +68,7 @@ at Assert.That(variable).IsFalse() }; var exception = await Assert.ThrowsAsync(action); - await Assert.That(exception.Message).IsEqualTo(expectedMessage); + await Assert.That(exception.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage.NormalizeLineEndings()); } [Test] @@ -91,7 +91,7 @@ await Assert.That(variable).IsTrue().Because(because) }; var exception = await Assert.ThrowsAsync(action); - await Assert.That(exception.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage); + await Assert.That(exception.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage.NormalizeLineEndings()); } [Test] diff --git a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.ExactlyTests.cs b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.ExactlyTests.cs index c3292ec78e..35d744499e 100644 --- a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.ExactlyTests.cs +++ b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.ExactlyTests.cs @@ -13,15 +13,15 @@ public async Task Fails_For_Code_With_Other_Exceptions() but threw TUnit.Assertions.Tests.Assertions.Delegates.Throws+OtherException at Assert.That(action).ThrowsExactly() - """; + """.NormalizeLineEndings(); Exception exception = CreateOtherException(); Action action = () => throw exception; var sut = async () => await Assert.That(action).ThrowsExactly(); - await Assert.That(sut).ThrowsException() - .WithMessage(expectedMessage); + var thrownException = await Assert.That(sut).ThrowsException(); + await Assert.That(thrownException.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage); } [Test] @@ -32,15 +32,15 @@ public async Task Fails_For_Code_With_Subtype_Exceptions() but wrong exception type: SubCustomException instead of exactly CustomException at Assert.That(action).ThrowsExactly() - """; + """.NormalizeLineEndings(); Exception exception = CreateSubCustomException(); Action action = () => throw exception; var sut = async () => await Assert.That(action).ThrowsExactly(); - await Assert.That(sut).ThrowsException() - .WithMessage(expectedMessage); + var thrownException = await Assert.That(sut).ThrowsException(); + await Assert.That(thrownException.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage); } [Test] @@ -51,14 +51,14 @@ public async Task Fails_For_Code_Without_Exceptions() but no exception was thrown at Assert.That(action).ThrowsExactly() - """; + """.NormalizeLineEndings(); var action = () => { }; var sut = async () => await Assert.That(action).ThrowsExactly(); - await Assert.That(sut).ThrowsException() - .WithMessage(expectedMessage); + var thrownException = await Assert.That(sut).ThrowsException(); + await Assert.That(thrownException.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage); } [Test] @@ -117,10 +117,11 @@ public async Task Conversion_To_Value_Assertion_Builder_On_Casted_Exception_Type await Assert.That((object)ex).IsAssignableTo(); }); - await Assert.That(assertionException).HasMessageStartingWith(""" - Expected to throw exactly Exception - but wrong exception type: CustomException instead of exactly Exception - """); + var expectedPrefix = """ + Expected to throw exactly Exception + but wrong exception type: CustomException instead of exactly Exception + """.NormalizeLineEndings(); + await Assert.That(assertionException.Message.NormalizeLineEndings()).StartsWith(expectedPrefix); } [Test] diff --git a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.ExceptionTests.cs b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.ExceptionTests.cs index cedfa0b176..3b51f1e29e 100644 --- a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.ExceptionTests.cs +++ b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.ExceptionTests.cs @@ -14,14 +14,14 @@ public async Task Fails_For_Code_Without_Exceptions() but no exception was thrown at Assert.That(action).ThrowsException() - """; + """.NormalizeLineEndings(); var action = () => { }; var sut = async () => await Assert.That(action).ThrowsException(); - await Assert.That(sut).ThrowsException() - .WithMessage(expectedMessage); + var thrownException = await Assert.That(sut).ThrowsException(); + await Assert.That(thrownException.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage); } [Test] diff --git a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.NothingTests.cs b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.NothingTests.cs index 48077a848d..29fc9e7d33 100644 --- a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.NothingTests.cs +++ b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.NothingTests.cs @@ -12,15 +12,15 @@ public async Task Fails_For_Code_With_Exceptions() but threw TUnit.Assertions.Tests.Assertions.Delegates.Throws+CustomException: {nameof(Fails_For_Code_With_Exceptions)} at Assert.That(action).ThrowsNothing() - """; + """.NormalizeLineEndings(); Exception exception = CreateCustomException(); Action action = () => throw exception; var sut = async () => await Assert.That(action).ThrowsNothing(); - await Assert.That(sut).ThrowsException() - .WithMessage(expectedMessage); + var thrownException = await Assert.That(sut).ThrowsException(); + await Assert.That(thrownException.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage); } [Test] diff --git a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.OfTypeTests.cs b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.OfTypeTests.cs index a900ceaff2..ef4c853221 100644 --- a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.OfTypeTests.cs +++ b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.OfTypeTests.cs @@ -13,15 +13,15 @@ public async Task Fails_For_Code_With_Other_Exceptions() but threw TUnit.Assertions.Tests.Assertions.Delegates.Throws+OtherException at Assert.That(action).Throws() - """; + """.NormalizeLineEndings(); Exception exception = CreateOtherException(); Action action = () => throw exception; var sut = async () => await Assert.That(action).Throws(); - await Assert.That(sut).ThrowsException() - .WithMessage(expectedMessage); + var thrownException = await Assert.That(sut).ThrowsException(); + await Assert.That(thrownException.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage); } [Test] @@ -32,15 +32,15 @@ public async Task Fails_For_Code_With_Supertype_Exceptions() but threw TUnit.Assertions.Tests.Assertions.Delegates.Throws+CustomException at Assert.That(action).Throws() - """; + """.NormalizeLineEndings(); Exception exception = CreateCustomException(); Action action = () => throw exception; var sut = async () => await Assert.That(action).Throws(); - await Assert.That(sut).ThrowsException() - .WithMessage(expectedMessage); + var thrownException = await Assert.That(sut).ThrowsException(); + await Assert.That(thrownException.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage); } [Test] @@ -51,14 +51,14 @@ public async Task Fails_For_Code_Without_Exceptions() but no exception was thrown at Assert.That(action).Throws() - """; + """.NormalizeLineEndings(); var action = () => { }; var sut = async () => await Assert.That(action).Throws(); - await Assert.That(sut).ThrowsException() - .WithMessage(expectedMessage); + var thrownException = await Assert.That(sut).ThrowsException(); + await Assert.That(thrownException.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage); } [Test] diff --git a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithInnerExceptionTests.cs b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithInnerExceptionTests.cs index 88386844cd..2b167014c5 100644 --- a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithInnerExceptionTests.cs +++ b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithInnerExceptionTests.cs @@ -15,7 +15,7 @@ Expected exception message to equal "bar" but exception message was "some different inner message" at Assert.That(action).ThrowsException().WithInnerException().WithMessage("bar") - """; + """.NormalizeLineEndings(); Exception exception = CreateCustomException(outerMessage, CreateCustomException("some different inner message")); Action action = () => throw exception; @@ -24,8 +24,8 @@ at Assert.That(action).ThrowsException().WithInnerException().WithMessage("bar") => await Assert.That(action).ThrowsException() .WithInnerException().WithMessage(expectedInnerMessage); - await Assert.That(sut).ThrowsException() - .WithMessage(expectedMessage); + var thrownException = await Assert.That(sut).ThrowsException(); + await Assert.That(thrownException.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage); } [Test] diff --git a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithMessageMatchingTests.cs b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithMessageMatchingTests.cs index 8401907110..0d8e0b37ff 100644 --- a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithMessageMatchingTests.cs +++ b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithMessageMatchingTests.cs @@ -42,15 +42,15 @@ Expected exception message to match pattern "bar" but exception message "foo" does not match pattern "bar" at Assert.That(action).ThrowsExactly().WithMessageMatching("bar") - """; + """.NormalizeLineEndings(); Exception exception = CreateCustomException(message1); Action action = () => throw exception; var sut = async () => await Assert.That(action).ThrowsExactly().WithMessageMatching(message2); - await Assert.That(sut).ThrowsException() - .WithMessage(expectedMessage); + var thrownException = await Assert.That(sut).ThrowsException(); + await Assert.That(thrownException.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage); } [Test] diff --git a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithMessageTests.cs b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithMessageTests.cs index d444dc3372..293d109d1e 100644 --- a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithMessageTests.cs +++ b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithMessageTests.cs @@ -15,15 +15,15 @@ Expected exception message to equal "bar" but exception message was "foo" at Assert.That(action).ThrowsExactly().WithMessage("bar") - """; + """.NormalizeLineEndings(); Exception exception = CreateCustomException(message1); Action action = () => throw exception; var sut = async () => await Assert.That(action).ThrowsExactly().WithMessage(message2); - await Assert.That(sut).ThrowsException() - .WithMessage(expectedMessage); + var thrownException = await Assert.That(sut).ThrowsException(); + await Assert.That(thrownException.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage); } [Test] diff --git a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithParameterNameTests.cs b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithParameterNameTests.cs index 41c2ca56c0..70c0c658f9 100644 --- a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithParameterNameTests.cs +++ b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithParameterNameTests.cs @@ -15,15 +15,15 @@ public async Task Fails_For_Different_Parameter_Name() but ArgumentException parameter name was "foo" at Assert.That(action).ThrowsExactly().WithParameterName("bar") - """; + """.NormalizeLineEndings(); ArgumentException exception = new(string.Empty, paramName1); Action action = () => throw exception; var sut = async () => await Assert.That(action).ThrowsExactly().WithParameterName(paramName2); - await Assert.That(sut).ThrowsException() - .WithMessage(expectedMessage); + var thrownException = await Assert.That(sut).ThrowsException(); + await Assert.That(thrownException.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage); } [Test] diff --git a/TUnit.Assertions.Tests/Bugs/Tests2117.cs b/TUnit.Assertions.Tests/Bugs/Tests2117.cs index df5fcad7cb..6fe04798e3 100644 --- a/TUnit.Assertions.Tests/Bugs/Tests2117.cs +++ b/TUnit.Assertions.Tests/Bugs/Tests2117.cs @@ -28,12 +28,13 @@ at Assert.That(a).IsEquivalentTo(b) """)] public async Task IsEquivalent_Fail(int[] a, int[] b, CollectionOrdering? collectionOrdering, string expectedError) { - await Assert.That(async () => + var exception = await Assert.That(async () => await (collectionOrdering is null ? Assert.That(a).IsEquivalentTo(b) : Assert.That(a).IsEquivalentTo(b, collectionOrdering.Value)) - ).Throws() - .WithMessage(expectedError); + ).Throws(); + + await Assert.That(exception.Message.NormalizeLineEndings()).IsEqualTo(expectedError.NormalizeLineEndings()); } [Test] @@ -60,11 +61,12 @@ at Assert.That(a).IsNotEquivalentTo(b) """)] public async Task IsNotEquivalent_Fail(int[] a, int[] b, CollectionOrdering? collectionOrdering, string expectedError) { - await Assert.That(async () => + var exception = await Assert.That(async () => await (collectionOrdering is null ? Assert.That(a).IsNotEquivalentTo(b) : Assert.That(a).IsNotEquivalentTo(b, collectionOrdering.Value)) - ).Throws() - .WithMessage(expectedError); + ).Throws(); + + await Assert.That(exception.Message.NormalizeLineEndings()).IsEqualTo(expectedError.NormalizeLineEndings()); } } diff --git a/TUnit.Assertions.Tests/CollectionAssertionTests.cs b/TUnit.Assertions.Tests/CollectionAssertionTests.cs new file mode 100644 index 0000000000..1cc925828e --- /dev/null +++ b/TUnit.Assertions.Tests/CollectionAssertionTests.cs @@ -0,0 +1,36 @@ +namespace TUnit.Assertions.Tests; + +public class CollectionAssertionTests +{ + [Test] + public async Task IsEmpty() + { + var items = new List(); + + await Assert.That(items).IsEmpty(); + } + + [Test] + public async Task IsEmpty2() + { + var items = new List(); + + await Assert.That(() => items).IsEmpty(); + } + + [Test] + public async Task Count() + { + var items = new List(); + + await Assert.That(items).Count().IsEqualTo(0); + } + + [Test] + public async Task Count2() + { + var items = new List(); + + await Assert.That(() => items).Count().IsEqualTo(0); + } +} diff --git a/TUnit.Assertions.Tests/FuncCollectionAssertionTests.cs b/TUnit.Assertions.Tests/FuncCollectionAssertionTests.cs new file mode 100644 index 0000000000..ffa4fee039 --- /dev/null +++ b/TUnit.Assertions.Tests/FuncCollectionAssertionTests.cs @@ -0,0 +1,229 @@ +using TUnit.Assertions.Exceptions; + +namespace TUnit.Assertions.Tests; + +/// +/// Tests for FuncCollectionAssertion - verifies that collection assertions work when +/// collections are wrapped in lambdas. Addresses GitHub issue #3910. +/// +public class FuncCollectionAssertionTests +{ + [Test] + public async Task Lambda_Collection_IsEmpty_Passes_For_Empty_Collection() + { + var items = new List(); + await Assert.That(() => items).IsEmpty(); + } + + [Test] + public async Task Lambda_Collection_IsEmpty_Fails_For_NonEmpty_Collection() + { + var items = new List { 1, 2, 3 }; + await Assert.That(async () => await Assert.That(() => items).IsEmpty()) + .Throws(); + } + + [Test] + public async Task Lambda_Collection_IsNotEmpty_Passes_For_NonEmpty_Collection() + { + var items = new List { 1, 2, 3 }; + await Assert.That(() => items).IsNotEmpty(); + } + + [Test] + public async Task Lambda_Collection_IsNotEmpty_Fails_For_Empty_Collection() + { + var items = new List(); + await Assert.That(async () => await Assert.That(() => items).IsNotEmpty()) + .Throws(); + } + + [Test] + public async Task Lambda_Collection_Count_IsEqualTo_Passes() + { + var items = new List { 1, 2, 3 }; + await Assert.That(() => items).Count().IsEqualTo(3); + } + + [Test] + public async Task Lambda_Collection_Count_IsGreaterThan_Passes() + { + var items = new List { 1, 2, 3, 4, 5 }; + await Assert.That(() => items).Count().IsGreaterThan(3); + } + + [Test] + public async Task Lambda_Collection_Contains_Passes() + { + var items = new List { 1, 2, 3 }; + await Assert.That(() => items).Contains(2); + } + + [Test] + public async Task Lambda_Collection_Contains_Fails_When_Item_Not_Present() + { + var items = new List { 1, 2, 3 }; + await Assert.That(async () => await Assert.That(() => items).Contains(99)) + .Throws(); + } + + [Test] + public async Task Lambda_Collection_DoesNotContain_Passes() + { + var items = new List { 1, 2, 3 }; + await Assert.That(() => items).DoesNotContain(99); + } + + [Test] + public async Task Lambda_Collection_HasSingleItem_Passes() + { + var items = new List { 42 }; + await Assert.That(() => items).HasSingleItem(); + } + + [Test] + public async Task Lambda_Collection_All_Passes() + { + var items = new List { 2, 4, 6 }; + await Assert.That(() => items).All(x => x % 2 == 0); + } + + [Test] + public async Task Lambda_Collection_Any_Passes() + { + var items = new List { 1, 2, 3 }; + await Assert.That(() => items).Any(x => x > 2); + } + + [Test] + public async Task Lambda_Collection_IsInOrder_Passes() + { + var items = new List { 1, 2, 3, 4, 5 }; + await Assert.That(() => items).IsInOrder(); + } + + [Test] + public async Task Lambda_Collection_IsInDescendingOrder_Passes() + { + var items = new List { 5, 4, 3, 2, 1 }; + await Assert.That(() => items).IsInDescendingOrder(); + } + + [Test] + public async Task Lambda_Collection_HasDistinctItems_Passes() + { + var items = new List { 1, 2, 3 }; + await Assert.That(() => items).HasDistinctItems(); + } + + [Test] + public async Task Lambda_Collection_Throws_Passes_When_Exception_Thrown() + { + await Assert.That(() => ThrowingMethod()).Throws(); + } + + [Test] + public async Task Lambda_Collection_Chaining_With_And() + { + var items = new List { 1, 2, 3 }; + await Assert.That(() => items) + .IsNotEmpty() + .And.Contains(2) + .And.HasDistinctItems(); + } + + [Test] + public async Task Lambda_Array_IsEmpty_Passes() + { + var items = Array.Empty(); + await Assert.That(() => items).IsEmpty(); + } + + [Test] + public async Task Lambda_Array_IsNotEmpty_Passes() + { + var items = new[] { "a", "b", "c" }; + await Assert.That(() => items).IsNotEmpty(); + } + + [Test] + public async Task Lambda_Enumerable_IsEmpty_Passes() + { + IEnumerable items = Enumerable.Empty(); + await Assert.That(() => items).IsEmpty(); + } + + [Test] + public async Task Lambda_HashSet_Contains_Passes() + { + var items = new HashSet { 1, 2, 3 }; + await Assert.That(() => items).Contains(2); + } + + private static IEnumerable ThrowingMethod() + { + throw new InvalidOperationException("Test exception"); + } + + // Async lambda tests + [Test] + public async Task AsyncLambda_Collection_IsEmpty_Passes() + { + await Assert.That(async () => await GetEmptyCollectionAsync()).IsEmpty(); + } + + [Test] + public async Task AsyncLambda_Collection_IsNotEmpty_Passes() + { + await Assert.That(async () => await GetCollectionAsync()).IsNotEmpty(); + } + + [Test] + public async Task AsyncLambda_Collection_Count_IsEqualTo_Passes() + { + await Assert.That(async () => await GetCollectionAsync()).Count().IsEqualTo(3); + } + + [Test] + public async Task AsyncLambda_Collection_Contains_Passes() + { + await Assert.That(async () => await GetCollectionAsync()).Contains(2); + } + + [Test] + public async Task AsyncLambda_Collection_All_Passes() + { + await Assert.That(async () => await GetCollectionAsync()).All(x => x > 0); + } + + [Test] + public async Task AsyncLambda_Collection_Throws_Passes() + { + await Assert.That(async () => await ThrowingMethodAsync()).Throws(); + } + + [Test] + public async Task AsyncLambda_Collection_Chaining_With_And() + { + await Assert.That(async () => await GetCollectionAsync()) + .IsNotEmpty() + .And.Contains(2) + .And.HasDistinctItems(); + } + + private static Task> GetEmptyCollectionAsync() + { + return Task.FromResult>(new List()); + } + + private static Task> GetCollectionAsync() + { + return Task.FromResult>(new List { 1, 2, 3 }); + } + + private static async Task> ThrowingMethodAsync() + { + await Task.Yield(); + throw new InvalidOperationException("Test exception"); + } +} diff --git a/TUnit.Assertions.Tests/Helpers/StringDifferenceTests.cs b/TUnit.Assertions.Tests/Helpers/StringDifferenceTests.cs index 8aee42cc8e..18d1ef01d7 100644 --- a/TUnit.Assertions.Tests/Helpers/StringDifferenceTests.cs +++ b/TUnit.Assertions.Tests/Helpers/StringDifferenceTests.cs @@ -10,15 +10,15 @@ Expected to be equal to "some text" but found "" at Assert.That(actual).IsEqualTo(expected) - """; + """.NormalizeLineEndings(); var actual = ""; var expected = "some text"; var sut = async () => await Assert.That(actual).IsEqualTo(expected); - await Assert.That(sut).ThrowsException() - .WithMessage(expectedMessage); + var exception = await Assert.That(sut).ThrowsException(); + await Assert.That(exception.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage); } [Test] @@ -29,15 +29,15 @@ Expected to be equal to "" but found "actual text" at Assert.That(actual).IsEqualTo(expected) - """; + """.NormalizeLineEndings(); var actual = "actual text"; var expected = ""; var sut = async () => await Assert.That(actual).IsEqualTo(expected); - await Assert.That(sut).ThrowsException() - .WithMessage(expectedMessage); + var exception = await Assert.That(sut).ThrowsException(); + await Assert.That(exception.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage); } [Test] @@ -48,15 +48,15 @@ Expected to be equal to "some text" but found "some" at Assert.That(actual).IsEqualTo(expected) - """; + """.NormalizeLineEndings(); var actual = "some"; var expected = "some text"; var sut = async () => await Assert.That(actual).IsEqualTo(expected); - await Assert.That(sut).ThrowsException() - .WithMessage(expectedMessage); + var exception = await Assert.That(sut).ThrowsException(); + await Assert.That(exception.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage); } [Test] @@ -67,14 +67,14 @@ Expected to be equal to "some" but found "some text" at Assert.That(actual).IsEqualTo(expected) - """; + """.NormalizeLineEndings(); var actual = "some text"; var expected = "some"; var sut = async () => await Assert.That(actual).IsEqualTo(expected); - await Assert.That(sut).ThrowsException() - .WithMessage(expectedMessage); + var exception = await Assert.That(sut).ThrowsException(); + await Assert.That(exception.Message.NormalizeLineEndings()).IsEqualTo(expectedMessage); } } diff --git a/TUnit.Assertions.Tests/Old/AssertMultipleTests.cs b/TUnit.Assertions.Tests/Old/AssertMultipleTests.cs index a12e4a5421..8f7ff94050 100644 --- a/TUnit.Assertions.Tests/Old/AssertMultipleTests.cs +++ b/TUnit.Assertions.Tests/Old/AssertMultipleTests.cs @@ -33,35 +33,35 @@ Expected to be 2 but found 1 at Assert.That(1).IsEqualTo(2) - """); + """.NormalizeLineEndings()); await TUnitAssert.That(exception2.Message.NormalizeLineEndings()).IsEqualTo(""" Expected to be 3 but found 2 at Assert.That(2).IsEqualTo(3) - """); + """.NormalizeLineEndings()); await TUnitAssert.That(exception3.Message.NormalizeLineEndings()).IsEqualTo(""" Expected to be 4 but found 3 at Assert.That(3).IsEqualTo(4) - """); + """.NormalizeLineEndings()); await TUnitAssert.That(exception4.Message.NormalizeLineEndings()).IsEqualTo(""" Expected to be 5 but found 4 at Assert.That(4).IsEqualTo(5) - """); + """.NormalizeLineEndings()); await TUnitAssert.That(exception5.Message.NormalizeLineEndings()).IsEqualTo(""" Expected to be 6 but found 5 at Assert.That(5).IsEqualTo(6) - """); + """.NormalizeLineEndings()); } [Test] @@ -93,7 +93,7 @@ or to be 3 but found 1 at Assert.That(1).IsEqualTo(2).Or.IsEqualTo(3) - """); + """.NormalizeLineEndings()); await TUnitAssert.That(exception2.Message.NormalizeLineEndings()).IsEqualTo(""" Expected to be 3 @@ -101,7 +101,7 @@ and to be 4 but found 2 at Assert.That(2).IsEqualTo(3).And.IsEqualTo(4) - """); + """.NormalizeLineEndings()); await TUnitAssert.That(exception3.Message.NormalizeLineEndings()).IsEqualTo(""" Expected to be 4 @@ -109,7 +109,7 @@ or to be 5 but found 3 at Assert.That(3).IsEqualTo(4).Or.IsEqualTo(5) - """); + """.NormalizeLineEndings()); await TUnitAssert.That(exception4.Message.NormalizeLineEndings()).IsEqualTo(""" Expected to be 5 @@ -117,7 +117,7 @@ and to be 6 but found 4 at Assert.That(4).IsEqualTo(5).And.IsEqualTo(6) - """); + """.NormalizeLineEndings()); await TUnitAssert.That(exception5.Message.NormalizeLineEndings()).IsEqualTo(""" Expected to be 6 @@ -125,7 +125,7 @@ or to be 7 but found 5 at Assert.That(5).IsEqualTo(6).Or.IsEqualTo(7) - """); + """.NormalizeLineEndings()); } [Test] @@ -176,48 +176,48 @@ Expected to be 2 but found 1 at Assert.That(1).IsEqualTo(2) - """); + """.NormalizeLineEndings()); await TUnitAssert.That(assertionException2.Message.NormalizeLineEndings()).IsEqualTo(""" Expected to be 3 but found 2 at Assert.That(2).IsEqualTo(3) - """); + """.NormalizeLineEndings()); await TUnitAssert.That(assertionException3.Message.NormalizeLineEndings()).IsEqualTo(""" Expected to be 4 but found 3 at Assert.That(3).IsEqualTo(4) - """); + """.NormalizeLineEndings()); await TUnitAssert.That(assertionException4.Message.NormalizeLineEndings()).IsEqualTo(""" Expected to be 5 but found 4 at Assert.That(4).IsEqualTo(5) - """); + """.NormalizeLineEndings()); await TUnitAssert.That(assertionException5.Message.NormalizeLineEndings()).IsEqualTo(""" Expected to be 6 but found 5 at Assert.That(5).IsEqualTo(6) - """); + """.NormalizeLineEndings()); await TUnitAssert.That(assertionException6.Message.NormalizeLineEndings()).IsEqualTo(""" Expected to be 7 but found 6 at Assert.That(6).IsEqualTo(7) - """); + """.NormalizeLineEndings()); await TUnitAssert.That(assertionException7.Message.NormalizeLineEndings()).IsEqualTo(""" Expected to be 8 but found 7 at Assert.That(7).IsEqualTo(8) - """); + """.NormalizeLineEndings()); } } diff --git a/TUnit.Assertions.Tests/Old/EquivalentAssertionTests.cs b/TUnit.Assertions.Tests/Old/EquivalentAssertionTests.cs index 841660fe8d..a420ea73d1 100644 --- a/TUnit.Assertions.Tests/Old/EquivalentAssertionTests.cs +++ b/TUnit.Assertions.Tests/Old/EquivalentAssertionTests.cs @@ -136,7 +136,7 @@ await TUnitAssert.That(exception!.Message.NormalizeLineEndings()).IsEqualTo( but collection item at index 1 does not match: expected 2, but was 5 at Assert.That(array).IsEquivalentTo(list, CollectionOrdering.Matching) - """ + """.NormalizeLineEndings() ); } @@ -155,7 +155,7 @@ await TUnitAssert.That(exception!.Message.NormalizeLineEndings()).IsEqualTo( but collection item at index 1 does not match: expected 2, but was 5 at Assert.That(array).IsEquivalentTo(list, CollectionOrdering.Matching) - """ + """.NormalizeLineEndings() ); } diff --git a/TUnit.Assertions.Tests/Old/StringRegexAssertionTests.cs b/TUnit.Assertions.Tests/Old/StringRegexAssertionTests.cs index 3c1bffb616..98b2bcae9f 100644 --- a/TUnit.Assertions.Tests/Old/StringRegexAssertionTests.cs +++ b/TUnit.Assertions.Tests/Old/StringRegexAssertionTests.cs @@ -56,13 +56,13 @@ public async Task Matches_WithInvalidPattern_StringPattern_Throws(Type exception return; } - await TUnitAssert.That(exception!.Message).IsEqualTo( + await TUnitAssert.That(exception!.Message.NormalizeLineEndings()).IsEqualTo( $""" Expected text match pattern but The regex "^\d+$" does not match with "{text}" at Assert.That(text).Matches(pattern) - """ + """.NormalizeLineEndings() ); } @@ -81,13 +81,13 @@ public async Task Matches_WithInvalidPattern_RegexPattern_Throws(Type exceptionT return; } - await TUnitAssert.That(exception!.Message).IsEqualTo( + await TUnitAssert.That(exception!.Message.NormalizeLineEndings()).IsEqualTo( $""" Expected text match pattern but The regex "^\d+$" does not match with "{text}" at Assert.That(text).Matches(pattern) - """ + """.NormalizeLineEndings() ); } @@ -110,13 +110,13 @@ public async Task Matches_WithInvalidPattern_GeneratedRegexPattern_Throws(Type e return; } - await TUnitAssert.That(exception!.Message).IsEqualTo( + await TUnitAssert.That(exception!.Message.NormalizeLineEndings()).IsEqualTo( $""" Expected text match regex but The regex "^\d+$" does not match with "Hello123World" at Assert.That(text).Matches(regex) - """ + """.NormalizeLineEndings() ); } #endif @@ -192,13 +192,13 @@ public async Task DoesNotMatch_WithInvalidPattern_StringPattern_Throws(Type exce return; } - await TUnitAssert.That(exception!.Message).IsEqualTo( + await TUnitAssert.That(exception!.Message.NormalizeLineEndings()).IsEqualTo( $""" Expected text to not match with pattern but The regex "^\d+$" matches with "{text}" at Assert.That(text).DoesNotMatch(pattern) - """ + """.NormalizeLineEndings() ); } @@ -217,13 +217,13 @@ public async Task DoesNotMatch_WithInvalidPattern_RegexPattern_Throws(Type excep return; } - await TUnitAssert.That(exception!.Message).IsEqualTo( + await TUnitAssert.That(exception!.Message.NormalizeLineEndings()).IsEqualTo( $""" Expected text to not match with pattern but The regex "^\d+$" matches with "{text}" at Assert.That(text).DoesNotMatch(pattern) - """ + """.NormalizeLineEndings() ); } @@ -246,13 +246,13 @@ public async Task DoesNotMatch_WithInvalidPattern_GeneratedRegexPattern_Throws(T return; } - await TUnitAssert.That(exception!.Message).IsEqualTo( + await TUnitAssert.That(exception!.Message.NormalizeLineEndings()).IsEqualTo( $""" Expected text to not match with regex but The regex "^\d+$" matches with "{text}" at Assert.That(text).DoesNotMatch(regex) - """ + """.NormalizeLineEndings() ); } #endif diff --git a/TUnit.Assertions.Tests/StringLengthAssertionTests.cs b/TUnit.Assertions.Tests/StringLengthAssertionTests.cs new file mode 100644 index 0000000000..87fd7e2ae2 --- /dev/null +++ b/TUnit.Assertions.Tests/StringLengthAssertionTests.cs @@ -0,0 +1,164 @@ +using TUnit.Assertions.Exceptions; + +namespace TUnit.Assertions.Tests; + +public class StringLengthAssertionTests +{ + [Test] + public async Task Length_IsEqualTo_Passes_When_Length_Matches() + { + var str = "Hello"; + await Assert.That(str).Length().IsEqualTo(5); + } + + [Test] + public async Task Length_IsEqualTo_Fails_When_Length_DoesNotMatch() + { + var str = "Hello"; + await Assert.That(async () => await Assert.That(str).Length().IsEqualTo(10)) + .Throws() + .And.HasMessageContaining("10"); + } + + [Test] + public async Task Length_IsGreaterThan_Passes_When_Length_IsGreater() + { + var str = "Hello, World!"; + await Assert.That(str).Length().IsGreaterThan(5); + } + + [Test] + public async Task Length_IsGreaterThan_Fails_When_Length_IsNotGreater() + { + var str = "Hi"; + await Assert.That(async () => await Assert.That(str).Length().IsGreaterThan(5)) + .Throws() + .And.HasMessageContaining("greater than") + .And.HasMessageContaining("5"); + } + + [Test] + public async Task Length_IsLessThan_Passes_When_Length_IsLess() + { + var str = "Hi"; + await Assert.That(str).Length().IsLessThan(10); + } + + [Test] + public async Task Length_IsLessThan_Fails_When_Length_IsNotLess() + { + var str = "Hello, World!"; + await Assert.That(async () => await Assert.That(str).Length().IsLessThan(5)) + .Throws() + .And.HasMessageContaining("less than") + .And.HasMessageContaining("5"); + } + + [Test] + public async Task Length_IsGreaterThanOrEqualTo_Passes_When_Equal() + { + var str = "Hello"; + await Assert.That(str).Length().IsGreaterThanOrEqualTo(5); + } + + [Test] + public async Task Length_IsGreaterThanOrEqualTo_Passes_When_Greater() + { + var str = "Hello, World!"; + await Assert.That(str).Length().IsGreaterThanOrEqualTo(5); + } + + [Test] + public async Task Length_IsGreaterThanOrEqualTo_Fails_When_Less() + { + var str = "Hi"; + await Assert.That(async () => await Assert.That(str).Length().IsGreaterThanOrEqualTo(5)) + .Throws() + .And.HasMessageContaining("greater than or equal to") + .And.HasMessageContaining("5"); + } + + [Test] + public async Task Length_IsLessThanOrEqualTo_Passes_When_Equal() + { + var str = "Hello"; + await Assert.That(str).Length().IsLessThanOrEqualTo(5); + } + + [Test] + public async Task Length_IsLessThanOrEqualTo_Passes_When_Less() + { + var str = "Hi"; + await Assert.That(str).Length().IsLessThanOrEqualTo(5); + } + + [Test] + public async Task Length_IsLessThanOrEqualTo_Fails_When_Greater() + { + var str = "Hello, World!"; + await Assert.That(async () => await Assert.That(str).Length().IsLessThanOrEqualTo(5)) + .Throws() + .And.HasMessageContaining("less than or equal to") + .And.HasMessageContaining("5"); + } + + [Test] + public async Task Length_IsBetween_Passes_When_InRange() + { + var str = "Hello"; + await Assert.That(str).Length().IsBetween(1, 10); + } + + [Test] + public async Task Length_IsBetween_Fails_When_OutOfRange() + { + var str = "Hello, World!"; + await Assert.That(async () => await Assert.That(str).Length().IsBetween(1, 5)) + .Throws() + .And.HasMessageContaining("between"); + } + + [Test] + public async Task Length_IsPositive_Passes_For_NonEmptyString() + { + var str = "Hello"; + await Assert.That(str).Length().IsPositive(); + } + + [Test] + public async Task Length_IsGreaterThanOrEqualTo_Zero_Passes_Always() + { + var str = ""; + await Assert.That(str).Length().IsGreaterThanOrEqualTo(0); + } + + [Test] + public async Task Length_IsZero_Passes_For_EmptyString() + { + var str = ""; + await Assert.That(str).Length().IsZero(); + } + + [Test] + public async Task Length_IsNotZero_Passes_For_NonEmptyString() + { + var str = "Hello"; + await Assert.That(str).Length().IsNotZero(); + } + + [Test] + public async Task Length_WithNullString_Returns_Zero() + { + string? str = null; + await Assert.That(str).Length().IsZero(); + } + + [Test] + public async Task Length_Chained_With_And() + { + var str = "Hello"; + await Assert.That(str) + .Length().IsGreaterThan(3) + .And.IsLessThan(10); + } +} diff --git a/TUnit.Assertions.Tests/ThrowInDelegateValueAssertionTests.cs b/TUnit.Assertions.Tests/ThrowInDelegateValueAssertionTests.cs index 86cec759e2..c776362562 100644 --- a/TUnit.Assertions.Tests/ThrowInDelegateValueAssertionTests.cs +++ b/TUnit.Assertions.Tests/ThrowInDelegateValueAssertionTests.cs @@ -5,18 +5,19 @@ public class ThrowInDelegateValueAssertionTests [Test] public async Task ThrowInDelegateValueAssertion_ReturnsExpectedErrorMessage() { + var expectedContains = """ + Expected to be equal to True + but threw System.Exception + """.NormalizeLineEndings(); var assertion = async () => await Assert.That(() => { throw new Exception("No"); return true; }).IsEqualTo(true); - await Assert.That(assertion) - .Throws() - .WithMessageContaining(""" - Expected to be equal to True - but threw System.Exception - """); + var exception = await Assert.That(assertion) + .Throws(); + await Assert.That(exception.Message.NormalizeLineEndings()).Contains(expectedContains); } [Test] diff --git a/TUnit.Assertions/Chaining/AndAssertion.cs b/TUnit.Assertions/Chaining/AndAssertion.cs index b9186e64ee..486c947b73 100644 --- a/TUnit.Assertions/Chaining/AndAssertion.cs +++ b/TUnit.Assertions/Chaining/AndAssertion.cs @@ -139,10 +139,10 @@ private string BuildCombinedExpectation() var becausePrefix = firstBecause.StartsWith("because ", StringComparison.OrdinalIgnoreCase) ? firstBecause : $"because {firstBecause}"; - return $"{firstExpectation}, {becausePrefix}{Environment.NewLine}and {secondExpectation}"; + return $"{firstExpectation}, {becausePrefix}\nand {secondExpectation}"; } - return $"{firstExpectation}{Environment.NewLine}and {secondExpectation}"; + return $"{firstExpectation}\nand {secondExpectation}"; } protected override string GetExpectation() => "both conditions"; diff --git a/TUnit.Assertions/Chaining/OrAssertion.cs b/TUnit.Assertions/Chaining/OrAssertion.cs index 47aa2e87c2..15b042a531 100644 --- a/TUnit.Assertions/Chaining/OrAssertion.cs +++ b/TUnit.Assertions/Chaining/OrAssertion.cs @@ -140,10 +140,10 @@ private string BuildCombinedExpectation() var becausePrefix = firstBecause.StartsWith("because ", StringComparison.OrdinalIgnoreCase) ? firstBecause : $"because {firstBecause}"; - return $"{firstExpectation}, {becausePrefix}{Environment.NewLine}or {secondExpectation}"; + return $"{firstExpectation}, {becausePrefix}\nor {secondExpectation}"; } - return $"{firstExpectation}{Environment.NewLine}or {secondExpectation}"; + return $"{firstExpectation}\nor {secondExpectation}"; } protected override string GetExpectation() => "either condition"; diff --git a/TUnit.Assertions/Conditions/CollectionComparerBasedAssertion.cs b/TUnit.Assertions/Conditions/CollectionComparerBasedAssertion.cs index 5a2f78fec1..cd168e5bba 100644 --- a/TUnit.Assertions/Conditions/CollectionComparerBasedAssertion.cs +++ b/TUnit.Assertions/Conditions/CollectionComparerBasedAssertion.cs @@ -1,4 +1,3 @@ -using System.Collections; using TUnit.Assertions.Core; using TUnit.Assertions.Sources; @@ -13,7 +12,7 @@ namespace TUnit.Assertions.Conditions; public abstract class CollectionComparerBasedAssertion : CollectionAssertionBase where TCollection : IEnumerable { - private IEqualityComparer? _comparer; + protected IEqualityComparer? Comparer; protected CollectionComparerBasedAssertion(AssertionContext context) : base(context) @@ -26,7 +25,7 @@ protected CollectionComparerBasedAssertion(AssertionContext context /// protected void SetComparer(IEqualityComparer comparer) { - _comparer = comparer; + Comparer = comparer; Context.ExpressionBuilder.Append($".Using({comparer.GetType().Name})"); } @@ -36,14 +35,6 @@ protected void SetComparer(IEqualityComparer comparer) /// protected IEqualityComparer GetComparer() { - return _comparer ?? EqualityComparer.Default; - } - - /// - /// Checks if a custom comparer has been specified. - /// - protected bool HasCustomComparer() - { - return _comparer != null; + return Comparer ?? EqualityComparer.Default; } } diff --git a/TUnit.Assertions/Conditions/EqualsAssertion.cs b/TUnit.Assertions/Conditions/EqualsAssertion.cs index 821a2b4e7f..350c439fad 100644 --- a/TUnit.Assertions/Conditions/EqualsAssertion.cs +++ b/TUnit.Assertions/Conditions/EqualsAssertion.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.Text; using TUnit.Assertions.Attributes; +using TUnit.Assertions.Conditions.Helpers; using TUnit.Assertions.Core; namespace TUnit.Assertions.Conditions; @@ -84,7 +85,7 @@ protected override Task CheckAsync(EvaluationMetadata m if (_ignoredTypes.Count > 0) { // Use reference-based tracking to detect cycles - var visited = new HashSet(new ReferenceEqualityComparer()); + var visited = new HashSet(ReferenceEqualityComparer.Instance); var result = DeepEquals(value, _expected, _ignoredTypes, visited); if (result.IsSuccess) { @@ -213,15 +214,4 @@ private static (bool IsSuccess, string? Message) DeepEquals(object? actual, obje } protected override string GetExpectation() => $"to be equal to {(_expected is string s ? $"\"{s}\"" : _expected)}"; - - /// - /// Comparer that uses reference equality instead of value equality. - /// Used for cycle detection in deep comparison. - /// - private sealed class ReferenceEqualityComparer : IEqualityComparer - { - public new bool Equals(object? x, object? y) => ReferenceEquals(x, y); - - public int GetHashCode(object obj) => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); - } } diff --git a/TUnit.Assertions/Conditions/Helpers/ExpressionHelper.cs b/TUnit.Assertions/Conditions/Helpers/ExpressionHelper.cs new file mode 100644 index 0000000000..4070c1c6a5 --- /dev/null +++ b/TUnit.Assertions/Conditions/Helpers/ExpressionHelper.cs @@ -0,0 +1,43 @@ +namespace TUnit.Assertions.Conditions.Helpers; + +/// +/// Helper methods for parsing and extracting information from assertion expressions. +/// Consolidates expression parsing logic to ensure consistent behavior across assertion classes. +/// +internal static class ExpressionHelper +{ + /// + /// Extracts the source variable name from an assertion expression string. + /// + /// The expression string, e.g., "Assert.That(variableName).IsEquivalentTo(...)" + /// The variable name, or "value" if it cannot be extracted or is a lambda expression. + /// + /// Input: "Assert.That(myObject).IsEquivalentTo(expected)" + /// Output: "myObject" + /// + /// Input: "Assert.That(async () => GetValue()).IsEquivalentTo(expected)" + /// Output: "value" + /// + public static string ExtractSourceVariable(string expression) + { + // Extract variable name from "Assert.That(variableName)" or similar + var thatIndex = expression.IndexOf(".That(", StringComparison.Ordinal); + if (thatIndex >= 0) + { + var startIndex = thatIndex + 6; // Length of ".That(" + var endIndex = expression.IndexOf(')', startIndex); + if (endIndex > startIndex) + { + var variable = expression.Substring(startIndex, endIndex - startIndex); + // Handle lambda expressions like "async () => ..." by returning "value" + if (variable.Contains("=>") || variable.StartsWith("()", StringComparison.Ordinal)) + { + return "value"; + } + return variable; + } + } + + return "value"; + } +} diff --git a/TUnit.Assertions/Conditions/Helpers/ReflectionHelper.cs b/TUnit.Assertions/Conditions/Helpers/ReflectionHelper.cs new file mode 100644 index 0000000000..c6c53162c5 --- /dev/null +++ b/TUnit.Assertions/Conditions/Helpers/ReflectionHelper.cs @@ -0,0 +1,63 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +namespace TUnit.Assertions.Conditions.Helpers; + +/// +/// Helper methods for reflection-based member access. +/// Consolidates reflection logic to ensure consistent behavior and reduce code duplication. +/// +internal static class ReflectionHelper +{ + /// + /// Gets all public instance properties and fields to compare for structural equivalency. + /// + /// The type to get members from. + /// A list of PropertyInfo and FieldInfo members. + public static List GetMembersToCompare( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] + Type type) + { + var members = new List(); + members.AddRange(type.GetProperties(BindingFlags.Public | BindingFlags.Instance)); + members.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.Instance)); + return members; + } + + /// + /// Gets the value of a member (property or field) from an object. + /// + /// The object to get the value from. + /// The member (PropertyInfo or FieldInfo) to read. + /// The value of the member. + /// Thrown if the member is not a PropertyInfo or FieldInfo. + public static object? GetMemberValue(object obj, MemberInfo member) + { + return member switch + { + PropertyInfo prop => prop.GetValue(obj), + FieldInfo field => field.GetValue(obj), + _ => throw new InvalidOperationException($"Unknown member type: {member.GetType()}") + }; + } + + /// + /// Gets a member (property or field) by name from a type. + /// + /// The type to search. + /// The member name to find. + /// The MemberInfo if found; null otherwise. + public static MemberInfo? GetMemberInfo( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] + Type type, + string name) + { + var property = type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance); + if (property != null) + { + return property; + } + + return type.GetField(name, BindingFlags.Public | BindingFlags.Instance); + } +} diff --git a/TUnit.Assertions/Conditions/Helpers/StructuralEqualityComparer.cs b/TUnit.Assertions/Conditions/Helpers/StructuralEqualityComparer.cs index af1a1876e9..13273b4a45 100644 --- a/TUnit.Assertions/Conditions/Helpers/StructuralEqualityComparer.cs +++ b/TUnit.Assertions/Conditions/Helpers/StructuralEqualityComparer.cs @@ -1,6 +1,5 @@ using System.Collections; using System.Diagnostics.CodeAnalysis; -using System.Reflection; namespace TUnit.Assertions.Conditions.Helpers; @@ -36,12 +35,12 @@ public bool Equals(T? x, T? y) var type = typeof(T); - if (IsPrimitiveType(type)) + if (TypeHelper.IsPrimitiveOrWellKnownType(type)) { return EqualityComparer.Default.Equals(x, y); } - return CompareStructurally(x, y, new HashSet(new ReferenceEqualityComparer())); + return CompareStructurally(x, y, new HashSet(ReferenceEqualityComparer.Instance)); } public int GetHashCode(T obj) @@ -54,23 +53,6 @@ public int GetHashCode(T obj) return EqualityComparer.Default.GetHashCode(obj); } - private static bool IsPrimitiveType(Type type) - { - return type.IsPrimitive - || type.IsEnum - || type == typeof(string) - || type == typeof(decimal) - || type == typeof(DateTime) - || type == typeof(DateTimeOffset) - || type == typeof(TimeSpan) - || type == typeof(Guid) -#if NET6_0_OR_GREATER - || type == typeof(DateOnly) - || type == typeof(TimeOnly) -#endif - ; - } - [UnconditionalSuppressMessage("Trimming", "IL2072", Justification = "GetType() is acceptable for runtime structural comparison")] private bool CompareStructurally(object? x, object? y, HashSet visited) { @@ -87,7 +69,7 @@ private bool CompareStructurally(object? x, object? y, HashSet visited) var xType = x.GetType(); var yType = y.GetType(); - if (IsPrimitiveType(xType)) + if (TypeHelper.IsPrimitiveOrWellKnownType(xType)) { return Equals(x, y); } @@ -121,12 +103,12 @@ private bool CompareStructurally(object? x, object? y, HashSet visited) return true; } - var members = GetMembersToCompare(xType); + var members = ReflectionHelper.GetMembersToCompare(xType); foreach (var member in members) { - var xValue = GetMemberValue(x, member); - var yValue = GetMemberValue(y, member); + var xValue = ReflectionHelper.GetMemberValue(x, member); + var yValue = ReflectionHelper.GetMemberValue(y, member); if (!CompareStructurally(xValue, yValue, visited)) { @@ -136,28 +118,4 @@ private bool CompareStructurally(object? x, object? y, HashSet visited) return true; } - - private static List GetMembersToCompare([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] Type type) - { - var members = new List(); - members.AddRange(type.GetProperties(BindingFlags.Public | BindingFlags.Instance)); - members.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.Instance)); - return members; - } - - private static object? GetMemberValue(object obj, MemberInfo member) - { - return member switch - { - PropertyInfo prop => prop.GetValue(obj), - FieldInfo field => field.GetValue(obj), - _ => throw new InvalidOperationException($"Unknown member type: {member.GetType()}") - }; - } - - private sealed class ReferenceEqualityComparer : IEqualityComparer - { - public new bool Equals(object? x, object? y) => ReferenceEquals(x, y); - public int GetHashCode(object obj) => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); - } } diff --git a/TUnit.Assertions/Conditions/Helpers/TypeHelper.cs b/TUnit.Assertions/Conditions/Helpers/TypeHelper.cs new file mode 100644 index 0000000000..856a05d9e6 --- /dev/null +++ b/TUnit.Assertions/Conditions/Helpers/TypeHelper.cs @@ -0,0 +1,84 @@ +using System.Collections.Concurrent; + +namespace TUnit.Assertions.Conditions.Helpers; + +/// +/// Helper methods for type checking and classification. +/// Consolidates type checking logic to ensure consistent behavior across assertion classes. +/// +internal static class TypeHelper +{ + /// + /// Thread-safe registry of user-defined types that should be treated as primitives + /// (using value equality rather than structural comparison). + /// + private static readonly ConcurrentDictionary CustomPrimitiveTypes = new(); + + /// + /// Registers a type to be treated as a primitive for structural equivalency comparisons. + /// Once registered, instances of this type will use value equality (via Equals) rather + /// than having their properties compared individually. + /// + /// The type to register as a primitive. + public static void RegisterAsPrimitive() + { + CustomPrimitiveTypes.TryAdd(typeof(T), 0); + } + + /// + /// Registers a type to be treated as a primitive for structural equivalency comparisons. + /// + /// The type to register as a primitive. + public static void RegisterAsPrimitive(Type type) + { + CustomPrimitiveTypes.TryAdd(type, 0); + } + + /// + /// Removes a previously registered custom primitive type. + /// + /// The type to unregister. + /// True if the type was removed; false if it wasn't registered. + public static bool UnregisterPrimitive() + { + return CustomPrimitiveTypes.TryRemove(typeof(T), out _); + } + + /// + /// Clears all registered custom primitive types. + /// Useful for test cleanup between tests. + /// + public static void ClearCustomPrimitives() + { + CustomPrimitiveTypes.Clear(); + } + + /// + /// Determines if a type is a primitive or well-known immutable type that should use + /// value equality rather than structural comparison. + /// + /// The type to check. + /// True if the type should use value equality; false for structural comparison. + public static bool IsPrimitiveOrWellKnownType(Type type) + { + // Check user-defined primitives first (fast path for common case) + if (CustomPrimitiveTypes.ContainsKey(type)) + { + return true; + } + + return type.IsPrimitive + || type.IsEnum + || type == typeof(string) + || type == typeof(decimal) + || type == typeof(DateTime) + || type == typeof(DateTimeOffset) + || type == typeof(TimeSpan) + || type == typeof(Guid) +#if NET6_0_OR_GREATER + || type == typeof(DateOnly) + || type == typeof(TimeOnly) +#endif + ; + } +} diff --git a/TUnit.Assertions/Conditions/IsEquivalentToAssertion.cs b/TUnit.Assertions/Conditions/IsEquivalentToAssertion.cs index 6eaf69bf8b..7592c54f0e 100644 --- a/TUnit.Assertions/Conditions/IsEquivalentToAssertion.cs +++ b/TUnit.Assertions/Conditions/IsEquivalentToAssertion.cs @@ -14,21 +14,19 @@ namespace TUnit.Assertions.Conditions; /// Inherits from CollectionComparerBasedAssertion to preserve collection type awareness in And/Or chains. /// [AssertionExtension("IsEquivalentTo")] -[RequiresUnreferencedCode("Collection equivalency uses structural comparison for complex objects, which requires reflection and is not compatible with AOT")] public class IsEquivalentToAssertion : CollectionComparerBasedAssertion where TCollection : IEnumerable { private readonly IEnumerable _expected; private readonly CollectionOrdering _ordering; + [RequiresUnreferencedCode("Collection equivalency uses structural comparison for complex objects, which requires reflection and is not compatible with AOT")] public IsEquivalentToAssertion( AssertionContext context, IEnumerable expected, CollectionOrdering ordering = CollectionOrdering.Any) - : base(context) + : this(context, expected, StructuralEqualityComparer.Instance, ordering) { - _expected = expected ?? throw new ArgumentNullException(nameof(expected)); - _ordering = ordering; } public IsEquivalentToAssertion( @@ -40,7 +38,7 @@ public IsEquivalentToAssertion( { _expected = expected ?? throw new ArgumentNullException(nameof(expected)); _ordering = ordering; - SetComparer(comparer); + Comparer = comparer; } public IsEquivalentToAssertion Using(IEqualityComparer comparer) @@ -49,7 +47,6 @@ public IsEquivalentToAssertion Using(IEqualityComparer CheckAsync(EvaluationMetadata metadata) { var value = metadata.Value; @@ -60,7 +57,7 @@ protected override Task CheckAsync(EvaluationMetadata.Instance; + var comparer = GetComparer(); var result = CollectionEquivalencyChecker.AreEquivalent( value, diff --git a/TUnit.Assertions/Conditions/NotEquivalentToAssertion.cs b/TUnit.Assertions/Conditions/NotEquivalentToAssertion.cs index 5fbb18d0d7..e32548337f 100644 --- a/TUnit.Assertions/Conditions/NotEquivalentToAssertion.cs +++ b/TUnit.Assertions/Conditions/NotEquivalentToAssertion.cs @@ -13,21 +13,31 @@ namespace TUnit.Assertions.Conditions; /// Inherits from CollectionComparerBasedAssertion to preserve collection type awareness in And/Or chains. /// [AssertionExtension("IsNotEquivalentTo")] -[RequiresUnreferencedCode("Collection equivalency uses structural comparison for complex objects, which requires reflection and is not compatible with AOT")] public class NotEquivalentToAssertion : CollectionComparerBasedAssertion where TCollection : IEnumerable { private readonly IEnumerable _notExpected; private readonly CollectionOrdering _ordering; + [RequiresUnreferencedCode("Collection equivalency uses structural comparison for complex objects, which requires reflection and is not compatible with AOT")] public NotEquivalentToAssertion( AssertionContext context, IEnumerable notExpected, CollectionOrdering ordering = CollectionOrdering.Any) + : this(context, notExpected, StructuralEqualityComparer.Instance, ordering) + { + } + + public NotEquivalentToAssertion( + AssertionContext context, + IEnumerable notExpected, + IEqualityComparer comparer, + CollectionOrdering ordering = CollectionOrdering.Any) : base(context) { _notExpected = notExpected ?? throw new ArgumentNullException(nameof(notExpected)); _ordering = ordering; + Comparer = comparer; } public NotEquivalentToAssertion Using(IEqualityComparer comparer) @@ -36,7 +46,6 @@ public NotEquivalentToAssertion Using(IEqualityComparer CheckAsync(EvaluationMetadata metadata) { var value = metadata.Value; @@ -47,7 +56,7 @@ protected override Task CheckAsync(EvaluationMetadata.Instance; + var comparer = GetComparer(); var result = CollectionEquivalencyChecker.AreEquivalent( value, diff --git a/TUnit.Assertions/Conditions/NotStructuralEquivalencyAssertion.cs b/TUnit.Assertions/Conditions/NotStructuralEquivalencyAssertion.cs index 4e58fd0bb6..2d8c30306a 100644 --- a/TUnit.Assertions/Conditions/NotStructuralEquivalencyAssertion.cs +++ b/TUnit.Assertions/Conditions/NotStructuralEquivalencyAssertion.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Text; +using TUnit.Assertions.Conditions.Helpers; using TUnit.Assertions.Core; namespace TUnit.Assertions.Conditions; @@ -90,7 +91,12 @@ protected override Task CheckAsync(EvaluationMetadata m foreach (var type in _ignoredTypes) tempAssertion.IgnoringType(type); - var result = tempAssertion.CompareObjects(value, _notExpected, "", new HashSet(new ReferenceEqualityComparer())); + var result = tempAssertion.CompareObjects( + value, + _notExpected, + "", + new HashSet(ReferenceEqualityComparer.Instance), + new HashSet(ReferenceEqualityComparer.Instance)); // Invert the result - we want them to NOT be equivalent if (result.IsPassed) @@ -101,43 +107,14 @@ protected override Task CheckAsync(EvaluationMetadata m return Task.FromResult(AssertionResult.Passed); } - private sealed class ReferenceEqualityComparer : IEqualityComparer - { - public new bool Equals(object? x, object? y) => ReferenceEquals(x, y); - public int GetHashCode(object obj) => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); - } - protected override string GetExpectation() { // Extract the source variable name from the expression builder // Format: "Assert.That(variableName).IsNotEquivalentTo(...)" var expressionString = Context.ExpressionBuilder.ToString(); - var sourceVariable = ExtractSourceVariable(expressionString); + var sourceVariable = ExpressionHelper.ExtractSourceVariable(expressionString); var notExpectedDesc = _notExpectedExpression ?? "expected value"; return $"{sourceVariable} to not be equivalent to {notExpectedDesc}"; } - - private static string ExtractSourceVariable(string expression) - { - // Extract variable name from "Assert.That(variableName)" or similar - var thatIndex = expression.IndexOf(".That("); - if (thatIndex >= 0) - { - var startIndex = thatIndex + 6; // Length of ".That(" - var endIndex = expression.IndexOf(')', startIndex); - if (endIndex > startIndex) - { - var variable = expression.Substring(startIndex, endIndex - startIndex); - // Handle lambda expressions like "async () => ..." by returning "value" - if (variable.Contains("=>") || variable.StartsWith("()")) - { - return "value"; - } - return variable; - } - } - - return "value"; - } } diff --git a/TUnit.Assertions/Conditions/StringLengthValueAssertion.cs b/TUnit.Assertions/Conditions/StringLengthValueAssertion.cs new file mode 100644 index 0000000000..faf24a95dc --- /dev/null +++ b/TUnit.Assertions/Conditions/StringLengthValueAssertion.cs @@ -0,0 +1,16 @@ +using TUnit.Assertions.Core; + +namespace TUnit.Assertions.Conditions; + +/// +/// Assertion that evaluates the length of a string and provides numeric assertions on that length. +/// Implements IAssertionSource<int> to enable all numeric assertion methods. +/// Example: await Assert.That(str).Length().IsGreaterThan(5); +/// +public class StringLengthValueAssertion : Sources.ValueAssertion +{ + public StringLengthValueAssertion(AssertionContext stringContext) + : base(stringContext.Map(s => s?.Length ?? 0)) + { + } +} diff --git a/TUnit.Assertions/Conditions/StructuralEquivalencyAssertion.cs b/TUnit.Assertions/Conditions/StructuralEquivalencyAssertion.cs index 8591bf4da9..2f1086a1a7 100644 --- a/TUnit.Assertions/Conditions/StructuralEquivalencyAssertion.cs +++ b/TUnit.Assertions/Conditions/StructuralEquivalencyAssertion.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text; +using TUnit.Assertions.Conditions.Helpers; using TUnit.Assertions.Core; namespace TUnit.Assertions.Conditions; @@ -77,11 +78,21 @@ protected override Task CheckAsync(EvaluationMetadata m return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}: {exception.Message}")); } - var result = CompareObjects(value, _expected, "", new HashSet(new ReferenceEqualityComparer())); + var result = CompareObjects( + value, + _expected, + "", + new HashSet(ReferenceEqualityComparer.Instance), + new HashSet(ReferenceEqualityComparer.Instance)); return Task.FromResult(result); } - internal AssertionResult CompareObjects(object? actual, object? expected, string path, HashSet visited) + internal AssertionResult CompareObjects( + object? actual, + object? expected, + string path, + HashSet visitedActual, + HashSet? visitedExpected = null) { // Check for ignored paths if (_ignoredMembers.Contains(path)) @@ -109,7 +120,7 @@ internal AssertionResult CompareObjects(object? actual, object? expected, string var expectedType = expected.GetType(); // Handle primitive types and strings - if (IsPrimitiveType(actualType)) + if (TypeHelper.IsPrimitiveOrWellKnownType(actualType)) { if (!Equals(actual, expected)) { @@ -118,13 +129,25 @@ internal AssertionResult CompareObjects(object? actual, object? expected, string return AssertionResult.Passed; } - // Handle cycles - if (visited.Contains(actual)) + // Handle cycles - check both actual and expected to prevent infinite recursion + // from cycles in either object graph + if (visitedActual.Contains(actual)) { return AssertionResult.Passed; } - visited.Add(actual); + visitedActual.Add(actual); + + // Also track expected objects to handle cycles in the expected graph + if (visitedExpected != null) + { + if (visitedExpected.Contains(expected)) + { + return AssertionResult.Passed; + } + + visitedExpected.Add(expected); + } // Handle enumerables if (actual is IEnumerable actualEnumerable && expected is IEnumerable expectedEnumerable @@ -156,7 +179,7 @@ internal AssertionResult CompareObjects(object? actual, object? expected, string return AssertionResult.Failed($"{itemPath} did not match{Environment.NewLine}Expected: null{Environment.NewLine}Received: {FormatValue(actualList[i])}"); } - var result = CompareObjects(actualList[i], expectedList[i], itemPath, visited); + var result = CompareObjects(actualList[i], expectedList[i], itemPath, visitedActual, visitedExpected); if (!result.IsPassed) { return result; @@ -167,7 +190,7 @@ internal AssertionResult CompareObjects(object? actual, object? expected, string } // Compare properties and fields - var expectedMembers = GetMembersToCompare(expectedType); + var expectedMembers = ReflectionHelper.GetMembersToCompare(expectedType); foreach (var member in expectedMembers) { @@ -178,7 +201,7 @@ internal AssertionResult CompareObjects(object? actual, object? expected, string continue; } - var expectedValue = GetMemberValue(expected, member); + var expectedValue = ReflectionHelper.GetMemberValue(expected, member); // Check if this member's type should be ignored var memberType = member switch @@ -198,25 +221,25 @@ internal AssertionResult CompareObjects(object? actual, object? expected, string // In partial equivalency mode, skip members that don't exist on actual if (_usePartialEquivalency) { - var actualMember = GetMemberInfo(actualType, member.Name); + var actualMember = ReflectionHelper.GetMemberInfo(actualType, member.Name); if (actualMember == null) { continue; } - actualValue = GetMemberValue(actual, actualMember); + actualValue = ReflectionHelper.GetMemberValue(actual, actualMember); } else { - var actualMember = GetMemberInfo(actualType, member.Name); + var actualMember = ReflectionHelper.GetMemberInfo(actualType, member.Name); if (actualMember == null) { return AssertionResult.Failed($"Property {memberPath} did not match{Environment.NewLine}Expected: {FormatValue(expectedValue)}{Environment.NewLine}Received: null"); } - actualValue = GetMemberValue(actual, actualMember); + actualValue = ReflectionHelper.GetMemberValue(actual, actualMember); } - var result = CompareObjects(actualValue, expectedValue, memberPath, visited); + var result = CompareObjects(actualValue, expectedValue, memberPath, visitedActual, visitedExpected); if (!result.IsPassed) { return result; @@ -226,7 +249,7 @@ internal AssertionResult CompareObjects(object? actual, object? expected, string // In non-partial mode, check for extra properties on actual if (!_usePartialEquivalency) { - var actualMembers = GetMembersToCompare(actualType); + var actualMembers = ReflectionHelper.GetMembersToCompare(actualType); var expectedMemberNames = new HashSet(expectedMembers.Select(m => m.Name)); foreach (var member in actualMembers) @@ -247,7 +270,7 @@ internal AssertionResult CompareObjects(object? actual, object? expected, string } var memberPath = string.IsNullOrEmpty(path) ? member.Name : $"{path}.{member.Name}"; - var actualValue = GetMemberValue(actual, member); + var actualValue = ReflectionHelper.GetMemberValue(actual, member); // Skip properties with null values - they're equivalent to not having the property if (actualValue == null) @@ -263,13 +286,6 @@ internal AssertionResult CompareObjects(object? actual, object? expected, string return AssertionResult.Passed; } - private static bool IsPrimitiveType(Type type) - { - return type.IsPrimitive || type.IsEnum || type == typeof(string) || type == typeof(decimal) - || type == typeof(DateTime) || type == typeof(DateTimeOffset) || type == typeof(TimeSpan) - || type == typeof(Guid); - } - private bool ShouldIgnoreType(Type type) { // Check if the type itself should be ignored @@ -288,36 +304,6 @@ private bool ShouldIgnoreType(Type type) return false; } - private static List GetMembersToCompare([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] Type type) - { - var members = new List(); - members.AddRange(type.GetProperties(BindingFlags.Public | BindingFlags.Instance)); - members.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.Instance)); - return members; - } - - private static MemberInfo? GetMemberInfo([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] Type type, string name) - { - var property = type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance); - if (property != null) - { - return property; - } - - var field = type.GetField(name, BindingFlags.Public | BindingFlags.Instance); - return field; - } - - private static object? GetMemberValue(object obj, MemberInfo member) - { - return member switch - { - PropertyInfo prop => prop.GetValue(obj), - FieldInfo field => field.GetValue(obj), - _ => throw new InvalidOperationException($"Unknown member type: {member.GetType()}") - }; - } - private static string FormatValue(object? value) { if (value == null) @@ -338,38 +324,9 @@ protected override string GetExpectation() // Extract the source variable name from the expression builder // Format: "Assert.That(variableName).IsEquivalentTo(...)" var expressionString = Context.ExpressionBuilder.ToString(); - var sourceVariable = ExtractSourceVariable(expressionString); + var sourceVariable = ExpressionHelper.ExtractSourceVariable(expressionString); var expectedDesc = _expectedExpression ?? "expected value"; return $"{sourceVariable} to be equivalent to {expectedDesc}"; } - - private static string ExtractSourceVariable(string expression) - { - // Extract variable name from "Assert.That(variableName)" or similar - var thatIndex = expression.IndexOf(".That("); - if (thatIndex >= 0) - { - var startIndex = thatIndex + 6; // Length of ".That(" - var endIndex = expression.IndexOf(')', startIndex); - if (endIndex > startIndex) - { - var variable = expression.Substring(startIndex, endIndex - startIndex); - // Handle lambda expressions like "async () => ..." by returning "value" - if (variable.Contains("=>") || variable.StartsWith("()")) - { - return "value"; - } - return variable; - } - } - - return "value"; - } - - private sealed class ReferenceEqualityComparer : IEqualityComparer - { - public new bool Equals(object? x, object? y) => ReferenceEquals(x, y); - public int GetHashCode(object obj) => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); - } } diff --git a/TUnit.Assertions/Extensions/Assert.cs b/TUnit.Assertions/Extensions/Assert.cs index 2e4a731f2e..c1835304b7 100644 --- a/TUnit.Assertions/Extensions/Assert.cs +++ b/TUnit.Assertions/Extensions/Assert.cs @@ -86,6 +86,20 @@ public static ValueAssertion That( return new ValueAssertion(value, expression); } + /// + /// Creates an assertion for a synchronous function that returns a collection. + /// This overload enables collection-specific assertions (IsEmpty, IsNotEmpty, HasCount, etc.) on lambda-wrapped collections. + /// Example: await Assert.That(() => GetItems()).IsEmpty(); + /// Example: await Assert.That(() => GetItems()).HasCount(5); + /// + [OverloadResolutionPriority(1)] + public static FuncCollectionAssertion That( + Func?> func, + [CallerArgumentExpression(nameof(func))] string? expression = null) + { + return new FuncCollectionAssertion(func!, expression); + } + /// /// Creates an assertion for a synchronous function. /// Example: await Assert.That(() => GetValue()).IsGreaterThan(10); @@ -97,6 +111,20 @@ public static FuncAssertion That( return new FuncAssertion(func, expression); } + /// + /// Creates an assertion for an asynchronous function that returns a collection. + /// This overload enables collection-specific assertions (IsEmpty, IsNotEmpty, HasCount, etc.) on async lambda-wrapped collections. + /// Example: await Assert.That(async () => await GetItemsAsync()).IsEmpty(); + /// Example: await Assert.That(async () => await GetItemsAsync()).HasCount(5); + /// + [OverloadResolutionPriority(1)] + public static AsyncFuncCollectionAssertion That( + Func?>> func, + [CallerArgumentExpression(nameof(func))] string? expression = null) + { + return new AsyncFuncCollectionAssertion(func!, expression); + } + /// /// Creates an assertion for an asynchronous function. /// Example: await Assert.That(async () => await GetValueAsync()).IsEqualTo(expected); diff --git a/TUnit.Assertions/Extensions/AssertionExtensions.cs b/TUnit.Assertions/Extensions/AssertionExtensions.cs index 8287c6ebdd..d065942ad1 100644 --- a/TUnit.Assertions/Extensions/AssertionExtensions.cs +++ b/TUnit.Assertions/Extensions/AssertionExtensions.cs @@ -683,10 +683,22 @@ private static string GetMemberPath(Expression 0 ? string.Join(".", parts) : "Unknown"; } + /// + /// Gets the length of the string as an integer for numeric assertions. + /// Example: await Assert.That(str).Length().IsGreaterThan(5); + /// + public static StringLengthValueAssertion Length( + this IAssertionSource source) + { + source.Context.ExpressionBuilder.Append(".Length()"); + return new StringLengthValueAssertion(source.Context); + } + /// /// Returns a wrapper for string length assertions. /// Example: await Assert.That(str).HasLength().EqualTo(5); /// + [Obsolete("Use Length() instead, which provides all numeric assertion methods. Example: Assert.That(str).Length().IsGreaterThan(5)")] public static LengthWrapper HasLength( this IAssertionSource source) { @@ -698,6 +710,7 @@ public static LengthWrapper HasLength( /// Asserts that the string has the expected length. /// Example: await Assert.That(str).HasLength(5); /// + [Obsolete("Use Length().IsEqualTo(expectedLength) instead.")] public static StringLengthAssertion HasLength( this IAssertionSource source, int expectedLength, diff --git a/TUnit.Assertions/Sources/AsyncFuncCollectionAssertion.cs b/TUnit.Assertions/Sources/AsyncFuncCollectionAssertion.cs new file mode 100644 index 0000000000..1e0512e4e7 --- /dev/null +++ b/TUnit.Assertions/Sources/AsyncFuncCollectionAssertion.cs @@ -0,0 +1,60 @@ +using System.Text; +using TUnit.Assertions.Conditions; +using TUnit.Assertions.Core; + +namespace TUnit.Assertions.Sources; + +/// +/// Source assertion for asynchronous functions that return collections. +/// This is the entry point for: Assert.That(async () => await GetCollectionAsync()) +/// Combines the lazy evaluation of AsyncFuncAssertion with the collection methods of CollectionAssertionBase. +/// Enables collection assertions like IsEmpty(), IsNotEmpty(), HasCount() on async lambda-wrapped collections. +/// +public class AsyncFuncCollectionAssertion : CollectionAssertionBase, TItem>, IDelegateAssertionSource> +{ + public AsyncFuncCollectionAssertion(Func?>> func, string? expression) + : base(CreateContext(func, expression)) + { + } + + private static AssertionContext> CreateContext(Func?>> func, string? expression) + { + var expressionBuilder = new StringBuilder(); + expressionBuilder.Append($"Assert.That({expression ?? "?"})"); + var evaluationContext = new EvaluationContext>(async () => + { + try + { + var result = await func().ConfigureAwait(false); + return (result, null); + } + catch (Exception ex) + { + return (default, ex); + } + }); + return new AssertionContext>(evaluationContext, expressionBuilder); + } + + /// + /// Asserts that the function throws the specified exception type (or subclass). + /// Example: await Assert.That(async () => await GetItemsAsync()).Throws<InvalidOperationException>(); + /// + public ThrowsAssertion Throws() where TException : Exception + { + Context.ExpressionBuilder.Append($".Throws<{typeof(TException).Name}>()"); + var mappedContext = Context.MapException(); + return new ThrowsAssertion(mappedContext!); + } + + /// + /// Asserts that the function throws exactly the specified exception type (not subclasses). + /// Example: await Assert.That(async () => await GetItemsAsync()).ThrowsExactly<InvalidOperationException>(); + /// + public ThrowsExactlyAssertion ThrowsExactly() where TException : Exception + { + Context.ExpressionBuilder.Append($".ThrowsExactly<{typeof(TException).Name}>()"); + var mappedContext = Context.MapException(); + return new ThrowsExactlyAssertion(mappedContext!); + } +} diff --git a/TUnit.Assertions/Sources/CollectionAssertionBase.cs b/TUnit.Assertions/Sources/CollectionAssertionBase.cs index 233e954ca4..d6e0ee81f4 100644 --- a/TUnit.Assertions/Sources/CollectionAssertionBase.cs +++ b/TUnit.Assertions/Sources/CollectionAssertionBase.cs @@ -109,6 +109,7 @@ public CollectionContainsPredicateAssertion Contains( /// This instance method enables calling HasCount with proper type inference. /// Example: await Assert.That(list).HasCount(5); /// + [Obsolete("Use Count().IsEqualTo(expectedCount) instead.")] public CollectionCountAssertion HasCount( int expectedCount, [CallerArgumentExpression(nameof(expectedCount))] string? expression = null) @@ -122,6 +123,7 @@ public CollectionCountAssertion HasCount( /// This enables the pattern: .HasCount().GreaterThan(5) /// Example: await Assert.That(list).HasCount().EqualTo(5); /// + [Obsolete("Use Count() instead, which provides all numeric assertion methods. Example: Assert.That(list).Count().IsGreaterThan(5)")] public CountWrapper HasCount() { Context.ExpressionBuilder.Append(".HasCount()"); diff --git a/TUnit.Assertions/Sources/FuncCollectionAssertion.cs b/TUnit.Assertions/Sources/FuncCollectionAssertion.cs new file mode 100644 index 0000000000..dc4117846a --- /dev/null +++ b/TUnit.Assertions/Sources/FuncCollectionAssertion.cs @@ -0,0 +1,60 @@ +using System.Text; +using TUnit.Assertions.Conditions; +using TUnit.Assertions.Core; + +namespace TUnit.Assertions.Sources; + +/// +/// Source assertion for synchronous functions that return collections. +/// This is the entry point for: Assert.That(() => GetCollection()) +/// Combines the lazy evaluation of FuncAssertion with the collection methods of CollectionAssertionBase. +/// Enables collection assertions like IsEmpty(), IsNotEmpty(), HasCount() on lambda-wrapped collections. +/// +public class FuncCollectionAssertion : CollectionAssertionBase, TItem>, IDelegateAssertionSource> +{ + public FuncCollectionAssertion(Func?> func, string? expression) + : base(CreateContext(func, expression)) + { + } + + private static AssertionContext> CreateContext(Func?> func, string? expression) + { + var expressionBuilder = new StringBuilder(); + expressionBuilder.Append($"Assert.That({expression ?? "?"})"); + var evaluationContext = new EvaluationContext>(() => + { + try + { + var result = func(); + return Task.FromResult<(IEnumerable?, Exception?)>((result, null)); + } + catch (Exception ex) + { + return Task.FromResult<(IEnumerable?, Exception?)>((default, ex)); + } + }); + return new AssertionContext>(evaluationContext, expressionBuilder); + } + + /// + /// Asserts that the function throws the specified exception type (or subclass). + /// Example: await Assert.That(() => GetItems()).Throws<InvalidOperationException>(); + /// + public ThrowsAssertion Throws() where TException : Exception + { + Context.ExpressionBuilder.Append($".Throws<{typeof(TException).Name}>()"); + var mappedContext = Context.MapException(); + return new ThrowsAssertion(mappedContext!); + } + + /// + /// Asserts that the function throws exactly the specified exception type (not subclasses). + /// Example: await Assert.That(() => GetItems()).ThrowsExactly<InvalidOperationException>(); + /// + public ThrowsExactlyAssertion ThrowsExactly() where TException : Exception + { + Context.ExpressionBuilder.Append($".ThrowsExactly<{typeof(TException).Name}>()"); + var mappedContext = Context.MapException(); + return new ThrowsExactlyAssertion(mappedContext!); + } +} diff --git a/TUnit.Core.SourceGenerator.Tests/ArgsAsArrayTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/ArgsAsArrayTests.Test.verified.txt index adcb5e7769..7cb140d823 100644 --- a/TUnit.Core.SourceGenerator.Tests/ArgsAsArrayTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/ArgsAsArrayTests.Test.verified.txt @@ -74,26 +74,40 @@ internal sealed class TUnit_TestProject_ArgsAsArrayTests_Params__string___TestSo switch (args.Length) { case 0: - instance.Params(new string[0]); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Params(new string[0]); + return default(global::System.Threading.Tasks.ValueTask); + } case 1: - instance.Params((args[0] is null ? null : args[0] is string[] arr ? arr : new string[] { TUnit.Core.Helpers.CastHelper.Cast(args[0]) })); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Params((args[0] is null ? null : args[0] is string[] arr ? arr : new string[] { TUnit.Core.Helpers.CastHelper.Cast(args[0]) })); + return default(global::System.Threading.Tasks.ValueTask); + } case 2: - instance.Params(new string[] { TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]) }); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Params(new string[] { TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]) }); + return default(global::System.Threading.Tasks.ValueTask); + } case 3: - instance.Params(new string[] { TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]) }); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Params(new string[] { TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]) }); + return default(global::System.Threading.Tasks.ValueTask); + } case 4: - instance.Params(new string[] { TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]) }); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Params(new string[] { TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]) }); + return default(global::System.Threading.Tasks.ValueTask); + } case 5: - instance.Params(new string[] { TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]) }); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Params(new string[] { TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]) }); + return default(global::System.Threading.Tasks.ValueTask); + } case 6: - instance.Params(new string[] { TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]), TUnit.Core.Helpers.CastHelper.Cast(args[5]) }); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Params(new string[] { TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]), TUnit.Core.Helpers.CastHelper.Cast(args[5]) }); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected between 0 and 1 arguments, but got {args.Length}"); } @@ -197,26 +211,40 @@ internal sealed class TUnit_TestProject_ArgsAsArrayTests_ParamsEnumerable__IEnum switch (args.Length) { case 0: - instance.ParamsEnumerable(TUnit.Core.Helpers.CastHelper.Cast>(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.ParamsEnumerable(TUnit.Core.Helpers.CastHelper.Cast>(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } case 1: - instance.ParamsEnumerable(TUnit.Core.Helpers.CastHelper.Cast>(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.ParamsEnumerable(TUnit.Core.Helpers.CastHelper.Cast>(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } case 2: - instance.ParamsEnumerable(TUnit.Core.Helpers.CastHelper.Cast>(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.ParamsEnumerable(TUnit.Core.Helpers.CastHelper.Cast>(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } case 3: - instance.ParamsEnumerable(TUnit.Core.Helpers.CastHelper.Cast>(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.ParamsEnumerable(TUnit.Core.Helpers.CastHelper.Cast>(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } case 4: - instance.ParamsEnumerable(TUnit.Core.Helpers.CastHelper.Cast>(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.ParamsEnumerable(TUnit.Core.Helpers.CastHelper.Cast>(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } case 5: - instance.ParamsEnumerable(TUnit.Core.Helpers.CastHelper.Cast>(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.ParamsEnumerable(TUnit.Core.Helpers.CastHelper.Cast>(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } case 6: - instance.ParamsEnumerable(TUnit.Core.Helpers.CastHelper.Cast>(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.ParamsEnumerable(TUnit.Core.Helpers.CastHelper.Cast>(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected between 0 and 1 arguments, but got {args.Length}"); } @@ -327,26 +355,40 @@ internal sealed class TUnit_TestProject_ArgsAsArrayTests_Following_Non_Params__i switch (args.Length) { case 1: - instance.Following_Non_Params(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast>(args[1])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Following_Non_Params(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast>(args[1])); + return default(global::System.Threading.Tasks.ValueTask); + } case 2: - instance.Following_Non_Params(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast>(args[1])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Following_Non_Params(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast>(args[1])); + return default(global::System.Threading.Tasks.ValueTask); + } case 3: - instance.Following_Non_Params(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast>(args[1])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Following_Non_Params(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast>(args[1])); + return default(global::System.Threading.Tasks.ValueTask); + } case 4: - instance.Following_Non_Params(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast>(args[1])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Following_Non_Params(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast>(args[1])); + return default(global::System.Threading.Tasks.ValueTask); + } case 5: - instance.Following_Non_Params(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast>(args[1])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Following_Non_Params(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast>(args[1])); + return default(global::System.Threading.Tasks.ValueTask); + } case 6: - instance.Following_Non_Params(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast>(args[1])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Following_Non_Params(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast>(args[1])); + return default(global::System.Threading.Tasks.ValueTask); + } case 7: - instance.Following_Non_Params(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast>(args[1])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Following_Non_Params(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast>(args[1])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected between 1 and 2 arguments, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/ArgumentWithImplicitConverterTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/ArgumentWithImplicitConverterTests.Test.verified.txt index e574acda8e..bb479a9d9d 100644 --- a/TUnit.Core.SourceGenerator.Tests/ArgumentWithImplicitConverterTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/ArgumentWithImplicitConverterTests.Test.verified.txt @@ -77,8 +77,10 @@ internal sealed class TUnit_TestProject_ArgumentWithImplicitConverterTests_Expli switch (args.Length) { case 1: - instance.Explicit(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Explicit(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -185,8 +187,10 @@ internal sealed class TUnit_TestProject_ArgumentWithImplicitConverterTests_Impli switch (args.Length) { case 1: - instance.Implicit(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Implicit(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/AsyncMethodDataSourceDrivenTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/AsyncMethodDataSourceDrivenTests.Test.verified.txt index a4e5d2b5ae..aeee5efd13 100644 --- a/TUnit.Core.SourceGenerator.Tests/AsyncMethodDataSourceDrivenTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/AsyncMethodDataSourceDrivenTests.Test.verified.txt @@ -96,7 +96,9 @@ internal sealed class TUnit_TestProject_AsyncMethodDataSourceDrivenTests_AsyncMe switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.AsyncMethodDataSource_SingleValue(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.AsyncMethodDataSource_SingleValue(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -229,7 +231,9 @@ internal sealed class TUnit_TestProject_AsyncMethodDataSourceDrivenTests_AsyncMe switch (args.Length) { case 2: - return new global::System.Threading.Tasks.ValueTask(instance.AsyncMethodDataSource_Tuples(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.AsyncMethodDataSource_Tuples(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + } default: throw new global::System.ArgumentException($"Expected exactly 2 arguments, but got {args.Length}"); } @@ -355,7 +359,9 @@ internal sealed class TUnit_TestProject_AsyncMethodDataSourceDrivenTests_AsyncMe switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.AsyncMethodDataSource_Enumerable(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.AsyncMethodDataSource_Enumerable(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -481,7 +487,9 @@ internal sealed class TUnit_TestProject_AsyncMethodDataSourceDrivenTests_AsyncMe switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.AsyncMethodDataSource_Func(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.AsyncMethodDataSource_Func(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -607,7 +615,9 @@ internal sealed class TUnit_TestProject_AsyncMethodDataSourceDrivenTests_AsyncMe switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.AsyncMethodDataSource_WithArguments(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.AsyncMethodDataSource_WithArguments(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -733,7 +743,9 @@ internal sealed class TUnit_TestProject_AsyncMethodDataSourceDrivenTests_AsyncMe switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.AsyncMethodDataSource_ExternalClass(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.AsyncMethodDataSource_ExternalClass(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -859,7 +871,9 @@ internal sealed class TUnit_TestProject_AsyncMethodDataSourceDrivenTests_ValueTa switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.ValueTaskMethodDataSource_SingleValue(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.ValueTaskMethodDataSource_SingleValue(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/ClassAndMethodArgumentsTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/ClassAndMethodArgumentsTests.Test.verified.txt index 9354f67153..1d7ccc4407 100644 --- a/TUnit.Core.SourceGenerator.Tests/ClassAndMethodArgumentsTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/ClassAndMethodArgumentsTests.Test.verified.txt @@ -197,7 +197,9 @@ internal sealed class TUnit_TestProject_ClassAndMethodArgumentsTests_WithMethodL switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.WithMethodLevel(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.WithMethodLevel(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -320,7 +322,9 @@ internal sealed class TUnit_TestProject_ClassAndMethodArgumentsTests_IgnoreParam switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.IgnoreParameters(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.IgnoreParameters(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/ClassDataSourceDrivenTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/ClassDataSourceDrivenTests.Test.verified.txt index 3187ccef56..01fcc949d8 100644 --- a/TUnit.Core.SourceGenerator.Tests/ClassDataSourceDrivenTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/ClassDataSourceDrivenTests.Test.verified.txt @@ -75,8 +75,10 @@ internal sealed class TUnit_TestProject_ClassDataSourceDrivenTests_DataSource_Cl switch (args.Length) { case 1: - instance.DataSource_Class(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.DataSource_Class(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -181,8 +183,10 @@ internal sealed class TUnit_TestProject_ClassDataSourceDrivenTests_DataSource_Cl switch (args.Length) { case 1: - instance.DataSource_Class_Generic(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.DataSource_Class_Generic(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -287,7 +291,9 @@ internal sealed class TUnit_TestProject_ClassDataSourceDrivenTests_IsInitialized switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.IsInitialized_With_1_ClassDataSource(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.IsInitialized_With_1_ClassDataSource(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -399,7 +405,9 @@ internal sealed class TUnit_TestProject_ClassDataSourceDrivenTests_IsInitialized switch (args.Length) { case 2: - return new global::System.Threading.Tasks.ValueTask(instance.IsInitialized_With_2_ClassDataSources(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.IsInitialized_With_2_ClassDataSources(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + } default: throw new global::System.ArgumentException($"Expected exactly 2 arguments, but got {args.Length}"); } @@ -518,7 +526,9 @@ internal sealed class TUnit_TestProject_ClassDataSourceDrivenTests_IsInitialized switch (args.Length) { case 3: - return new global::System.Threading.Tasks.ValueTask(instance.IsInitialized_With_3_ClassDataSources(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.IsInitialized_With_3_ClassDataSources(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]))); + } default: throw new global::System.ArgumentException($"Expected exactly 3 arguments, but got {args.Length}"); } @@ -644,7 +654,9 @@ internal sealed class TUnit_TestProject_ClassDataSourceDrivenTests_IsInitialized switch (args.Length) { case 4: - return new global::System.Threading.Tasks.ValueTask(instance.IsInitialized_With_4_ClassDataSources(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.IsInitialized_With_4_ClassDataSources(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]))); + } default: throw new global::System.ArgumentException($"Expected exactly 4 arguments, but got {args.Length}"); } @@ -777,7 +789,9 @@ internal sealed class TUnit_TestProject_ClassDataSourceDrivenTests_IsInitialized switch (args.Length) { case 5: - return new global::System.Threading.Tasks.ValueTask(instance.IsInitialized_With_5_ClassDataSources(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.IsInitialized_With_5_ClassDataSources(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]))); + } default: throw new global::System.ArgumentException($"Expected exactly 5 arguments, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/ClassDataSourceDrivenTestsSharedKeyed.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/ClassDataSourceDrivenTestsSharedKeyed.Test.verified.txt index 23866673ce..0ed69b261f 100644 --- a/TUnit.Core.SourceGenerator.Tests/ClassDataSourceDrivenTestsSharedKeyed.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/ClassDataSourceDrivenTestsSharedKeyed.Test.verified.txt @@ -76,8 +76,10 @@ internal sealed class TUnit_TestProject_ClassDataSourceDrivenTestsSharedKeyed_Da switch (args.Length) { case 1: - instance.DataSource_Class(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.DataSource_Class(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -183,8 +185,10 @@ internal sealed class TUnit_TestProject_ClassDataSourceDrivenTestsSharedKeyed_Da switch (args.Length) { case 1: - instance.DataSource_Class_Generic(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.DataSource_Class_Generic(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/ClassTupleDataSourceDrivenTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/ClassTupleDataSourceDrivenTests.Test.verified.txt index dd7c2f5de3..d5dd55b3e5 100644 --- a/TUnit.Core.SourceGenerator.Tests/ClassTupleDataSourceDrivenTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/ClassTupleDataSourceDrivenTests.Test.verified.txt @@ -364,8 +364,10 @@ internal sealed class TUnit_TestProject_ClassTupleDataSourceDrivenTests_DataSour switch (args.Length) { case 3: - instance.DataSource_TupleMethod(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.DataSource_TupleMethod(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 3 arguments, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/ConstantArgumentsTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/ConstantArgumentsTests.Test.verified.txt index 582a7cb6ec..1f18d3968c 100644 --- a/TUnit.Core.SourceGenerator.Tests/ConstantArgumentsTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/ConstantArgumentsTests.Test.verified.txt @@ -75,7 +75,9 @@ internal sealed class TUnit_TestProject_ConstantArgumentsTests_String1__string_T switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.String1(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.String1(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -180,7 +182,9 @@ internal sealed class TUnit_TestProject_ConstantArgumentsTests_Int__int_TestSour switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Int(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Int(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -285,7 +289,9 @@ internal sealed class TUnit_TestProject_ConstantArgumentsTests_Double__double_Te switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Double(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Double(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -390,7 +396,9 @@ internal sealed class TUnit_TestProject_ConstantArgumentsTests_Float__float_Test switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Float(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Float(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -495,7 +503,9 @@ internal sealed class TUnit_TestProject_ConstantArgumentsTests_Long__long_TestSo switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Long(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Long(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -600,7 +610,9 @@ internal sealed class TUnit_TestProject_ConstantArgumentsTests_UInt__uint_TestSo switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.UInt(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.UInt(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -705,7 +717,9 @@ internal sealed class TUnit_TestProject_ConstantArgumentsTests_ULong__ulong_Test switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.ULong(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.ULong(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/ConstantInBaseClassTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/ConstantInBaseClassTests.Test.verified.txt index 33ca6c1b5e..823766c246 100644 --- a/TUnit.Core.SourceGenerator.Tests/ConstantInBaseClassTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/ConstantInBaseClassTests.Test.verified.txt @@ -75,7 +75,9 @@ internal sealed class TUnit_TestProject_Bugs__1432_ConstantInBaseClassTests_Some switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.SomeTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.SomeTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/ConstantsInInterpolatedStringsTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/ConstantsInInterpolatedStringsTests.Test.verified.txt index 92048fa458..640e00c2d5 100644 --- a/TUnit.Core.SourceGenerator.Tests/ConstantsInInterpolatedStringsTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/ConstantsInInterpolatedStringsTests.Test.verified.txt @@ -75,7 +75,9 @@ internal sealed class TUnit_TestProject_Bugs__1432_ConstantsInInterpolatedString switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.SomeTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.SomeTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/CustomDisplayNameTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/CustomDisplayNameTests.Test.verified.txt index f28457c175..f5b91c08ec 100644 --- a/TUnit.Core.SourceGenerator.Tests/CustomDisplayNameTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/CustomDisplayNameTests.Test.verified.txt @@ -306,7 +306,9 @@ internal sealed class TUnit_TestProject_CustomDisplayNameTests_Test3__string_int switch (args.Length) { case 3: - return new global::System.Threading.Tasks.ValueTask(instance.Test3(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Test3(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]))); + } default: throw new global::System.ArgumentException($"Expected exactly 3 arguments, but got {args.Length}"); } @@ -436,7 +438,9 @@ internal sealed class TUnit_TestProject_CustomDisplayNameTests_MethodDataSourceT switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.MethodDataSourceTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.MethodDataSourceTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -564,7 +568,9 @@ internal sealed class TUnit_TestProject_CustomDisplayNameTests_TestParameterName switch (args.Length) { case 2: - return new global::System.Threading.Tasks.ValueTask(instance.TestParameterNamePrefixBug(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.TestParameterNamePrefixBug(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + } default: throw new global::System.ArgumentException($"Expected exactly 2 arguments, but got {args.Length}"); } @@ -682,7 +688,9 @@ internal sealed class TUnit_TestProject_CustomDisplayNameTests_PasswordTest__str switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.PasswordTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.PasswordTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/DataDrivenTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/DataDrivenTests.Test.verified.txt index c49443a6ac..75c55bca72 100644 --- a/TUnit.Core.SourceGenerator.Tests/DataDrivenTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/DataDrivenTests.Test.verified.txt @@ -77,8 +77,10 @@ internal sealed class TUnit_TestProject_DataDrivenTests_DataSource_Method__int_T switch (args.Length) { case 1: - instance.DataSource_Method(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.DataSource_Method(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -192,8 +194,10 @@ internal sealed class TUnit_TestProject_DataDrivenTests_DataSource_Method__int_s switch (args.Length) { case 2: - instance.DataSource_Method(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.DataSource_Method(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 2 arguments, but got {args.Length}"); } @@ -300,8 +304,10 @@ internal sealed class TUnit_TestProject_DataDrivenTests_EnumValue__TestEnum_Test switch (args.Length) { case 1: - instance.EnumValue(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.EnumValue(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -406,8 +412,10 @@ internal sealed class TUnit_TestProject_DataDrivenTests_NullValue__string__TestS switch (args.Length) { case 1: - instance.NullValue(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.NullValue(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -512,8 +520,10 @@ internal sealed class TUnit_TestProject_DataDrivenTests_EmptyString__string__Tes switch (args.Length) { case 1: - instance.EmptyString(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.EmptyString(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -618,8 +628,10 @@ internal sealed class TUnit_TestProject_DataDrivenTests_NonEmptyString__string__ switch (args.Length) { case 1: - instance.NonEmptyString(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.NonEmptyString(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -726,8 +738,10 @@ internal sealed class TUnit_TestProject_DataDrivenTests_BooleanString__bool__Tes switch (args.Length) { case 1: - instance.BooleanString(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.BooleanString(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -832,8 +846,10 @@ internal sealed class TUnit_TestProject_DataDrivenTests_Type__Type_TestSource : switch (args.Length) { case 1: - instance.Type(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Type(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -938,8 +954,10 @@ internal sealed class TUnit_TestProject_DataDrivenTests_IntegerArray__int___Test switch (args.Length) { case 1: - instance.IntegerArray(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.IntegerArray(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -1044,8 +1062,10 @@ internal sealed class TUnit_TestProject_DataDrivenTests_IntMaxValue__int_TestSou switch (args.Length) { case 1: - instance.IntMaxValue(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.IntMaxValue(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/DataSourceClassCombinedWithDataSourceMethodTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/DataSourceClassCombinedWithDataSourceMethodTests.Test.verified.txt index f3385cb194..e7e7b03dce 100644 --- a/TUnit.Core.SourceGenerator.Tests/DataSourceClassCombinedWithDataSourceMethodTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/DataSourceClassCombinedWithDataSourceMethodTests.Test.verified.txt @@ -163,8 +163,10 @@ internal sealed class TUnit_TestProject_DataSourceClassCombinedWithDataSourceMet switch (args.Length) { case 1: - instance.DataSourceClassCombinedWithDataSourceMethodTest(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.DataSourceClassCombinedWithDataSourceMethodTest(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/DataSourceGeneratorTests.Typed.verified.txt b/TUnit.Core.SourceGenerator.Tests/DataSourceGeneratorTests.Typed.verified.txt index 9bd5b95edd..85fe8ddee5 100644 --- a/TUnit.Core.SourceGenerator.Tests/DataSourceGeneratorTests.Typed.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/DataSourceGeneratorTests.Typed.verified.txt @@ -107,8 +107,10 @@ internal sealed class TUnit_TestProject_DataSourceGeneratorTests_GeneratedData_M switch (args.Length) { case 1: - instance.GeneratedData_Method(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.GeneratedData_Method(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -259,8 +261,10 @@ internal sealed class TUnit_TestProject_DataSourceGeneratorTests_GeneratedData_M switch (args.Length) { case 3: - instance.GeneratedData_Method2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.GeneratedData_Method2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 3 arguments, but got {args.Length}"); } @@ -411,8 +415,10 @@ internal sealed class TUnit_TestProject_DataSourceGeneratorTests_GeneratedData_M switch (args.Length) { case 3: - instance.GeneratedData_Method3(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.GeneratedData_Method3(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 3 arguments, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/DataSourceGeneratorTests.Untyped.verified.txt b/TUnit.Core.SourceGenerator.Tests/DataSourceGeneratorTests.Untyped.verified.txt index bd26b22a7c..5a4bbdb52f 100644 --- a/TUnit.Core.SourceGenerator.Tests/DataSourceGeneratorTests.Untyped.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/DataSourceGeneratorTests.Untyped.verified.txt @@ -95,7 +95,9 @@ internal sealed class TUnit_TestProject_AutoDataTests_Test1__string_int_double_b switch (args.Length) { case 4: - return new global::System.Threading.Tasks.ValueTask(instance.Test1(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Test1(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]))); + } default: throw new global::System.ArgumentException($"Expected exactly 4 arguments, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/DecimalArgumentTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/DecimalArgumentTests.Test.verified.txt index 894195d62b..f10cb90061 100644 --- a/TUnit.Core.SourceGenerator.Tests/DecimalArgumentTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/DecimalArgumentTests.Test.verified.txt @@ -84,7 +84,9 @@ internal sealed class TUnit_TestProject_DecimalArgumentTests_Transfer__decimal_d switch (args.Length) { case 2: - return new global::System.Threading.Tasks.ValueTask(instance.Transfer(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Transfer(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + } default: throw new global::System.ArgumentException($"Expected exactly 2 arguments, but got {args.Length}"); } @@ -189,7 +191,9 @@ internal sealed class TUnit_TestProject_DecimalArgumentTests_SimpleDecimal__deci switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.SimpleDecimal(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.SimpleDecimal(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -294,7 +298,9 @@ internal sealed class TUnit_TestProject_DecimalArgumentTests_SmallDecimal__decim switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.SmallDecimal(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.SmallDecimal(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -399,7 +405,9 @@ internal sealed class TUnit_TestProject_DecimalArgumentTests_MaxDecimal__decimal switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.MaxDecimal(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.MaxDecimal(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -504,7 +512,9 @@ internal sealed class TUnit_TestProject_DecimalArgumentTests_MinDecimal__decimal switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.MinDecimal(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.MinDecimal(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -609,7 +619,9 @@ internal sealed class TUnit_TestProject_DecimalArgumentTests_ExplicitDecimalValu switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.ExplicitDecimalValue(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.ExplicitDecimalValue(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -728,7 +740,9 @@ internal sealed class TUnit_TestProject_DecimalArgumentTests_MultipleDecimals__d switch (args.Length) { case 3: - return new global::System.Threading.Tasks.ValueTask(instance.MultipleDecimals(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.MultipleDecimals(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]))); + } default: throw new global::System.ArgumentException($"Expected exactly 3 arguments, but got {args.Length}"); } @@ -835,8 +849,10 @@ internal sealed class TUnit_TestProject_DecimalArgumentTests_Test__decimal_TestS switch (args.Length) { case 1: - instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -978,8 +994,10 @@ internal sealed class TUnit_TestProject_DecimalArgumentTests_TransactionDiscount switch (args.Length) { case 6: - instance.TransactionDiscountCalculations(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]), TUnit.Core.Helpers.CastHelper.Cast(args[5])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.TransactionDiscountCalculations(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]), TUnit.Core.Helpers.CastHelper.Cast(args[5])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 6 arguments, but got {args.Length}"); } @@ -1084,7 +1102,9 @@ internal sealed class TUnit_TestProject_DecimalArgumentTests_Equality3__decimal_ switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Equality3(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Equality3(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -1189,7 +1209,9 @@ internal sealed class TUnit_TestProject_DecimalArgumentTests_Equality4__decimal_ switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Equality4(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Equality4(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -1294,7 +1316,9 @@ internal sealed class TUnit_TestProject_DecimalArgumentTests_TestMethod__decimal switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.TestMethod(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.TestMethod(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/EnumMemberNamesTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/EnumMemberNamesTests.Test.verified.txt index 7b793e1562..208b4be63b 100644 --- a/TUnit.Core.SourceGenerator.Tests/EnumMemberNamesTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/EnumMemberNamesTests.Test.verified.txt @@ -77,8 +77,10 @@ internal sealed class TUnit_TestProject_Bugs__1432_EnumMemberNamesTests_SomeTest switch (args.Length) { case 1: - instance.SomeTest(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.SomeTest(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/EnumerableDataSourceDrivenTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/EnumerableDataSourceDrivenTests.Test.verified.txt index 6ba8139e62..1dbabb774a 100644 --- a/TUnit.Core.SourceGenerator.Tests/EnumerableDataSourceDrivenTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/EnumerableDataSourceDrivenTests.Test.verified.txt @@ -96,7 +96,9 @@ internal sealed class TUnit_TestProject_EnumerableDataSourceDrivenTests_DataSour switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.DataSource_Method(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.DataSource_Method(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -222,7 +224,9 @@ internal sealed class TUnit_TestProject_EnumerableDataSourceDrivenTests_DataSour switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.DataSource_Method2(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.DataSource_Method2(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -348,8 +352,10 @@ internal sealed class TUnit_TestProject_EnumerableDataSourceDrivenTests_DataSour switch (args.Length) { case 1: - instance.DataSource_WithBaseReturn(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.DataSource_WithBaseReturn(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/EnumerableTupleDataSourceDrivenTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/EnumerableTupleDataSourceDrivenTests.Test.verified.txt index 3a9a293f16..798797ad0e 100644 --- a/TUnit.Core.SourceGenerator.Tests/EnumerableTupleDataSourceDrivenTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/EnumerableTupleDataSourceDrivenTests.Test.verified.txt @@ -132,8 +132,10 @@ internal sealed class TUnit_TestProject_EnumerableTupleDataSourceDrivenTests_Dat switch (args.Length) { case 3: - instance.DataSource_TupleMethod(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.DataSource_TupleMethod(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 3 arguments, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/ExpectedArgumentTypeTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/ExpectedArgumentTypeTests.Test.verified.txt index bda4e49e2b..d7b8d38a28 100644 --- a/TUnit.Core.SourceGenerator.Tests/ExpectedArgumentTypeTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/ExpectedArgumentTypeTests.Test.verified.txt @@ -94,7 +94,9 @@ internal sealed class TUnit_TestProject_ExpectedArgumentTypeTests_TypedArguments switch (args.Length) { case 2: - return new global::System.Threading.Tasks.ValueTask(instance.TypedArguments(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.TypedArguments(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + } default: throw new global::System.ArgumentException($"Expected exactly 2 arguments, but got {args.Length}"); } @@ -220,7 +222,9 @@ internal sealed class TUnit_TestProject_ExpectedArgumentTypeTests_EnumTypes__obj switch (args.Length) { case 3: - return new global::System.Threading.Tasks.ValueTask(instance.EnumTypes(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.EnumTypes(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]))); + } default: throw new global::System.ArgumentException($"Expected exactly 3 arguments, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/GenericTypeResolverTests.cs b/TUnit.Core.SourceGenerator.Tests/GenericTypeResolverTests.cs index c074eb0d5f..40b105d8fe 100644 --- a/TUnit.Core.SourceGenerator.Tests/GenericTypeResolverTests.cs +++ b/TUnit.Core.SourceGenerator.Tests/GenericTypeResolverTests.cs @@ -148,7 +148,7 @@ public void TestMethod() private async Task RunTestWithInlineSource(string source) { var tempFile = Path.GetTempFileName() + ".cs"; - await FilePolyfill.WriteAllTextAsync(tempFile, source); + await File.WriteAllTextAsync(tempFile, source); try { diff --git a/TUnit.Core.SourceGenerator.Tests/InheritedTestsFromDifferentProjectTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/InheritedTestsFromDifferentProjectTests.Test.verified.txt index 2994f8ddff..219be38513 100644 --- a/TUnit.Core.SourceGenerator.Tests/InheritedTestsFromDifferentProjectTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/InheritedTestsFromDifferentProjectTests.Test.verified.txt @@ -167,8 +167,10 @@ internal sealed class TUnit_TestProject_InheritedTestsFromDifferentProjectTests_ switch (args.Length) { case 1: - instance.GenericMethodDataSource(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.GenericMethodDataSource(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -286,8 +288,10 @@ internal sealed class TUnit_TestProject_InheritedTestsFromDifferentProjectTests_ switch (args.Length) { case 1: - instance.NonGenericMethodDataSource(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.NonGenericMethodDataSource(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/InheritsTestsTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/InheritsTestsTests.Test.verified.txt index 94339aecb1..0f192ff3f5 100644 --- a/TUnit.Core.SourceGenerator.Tests/InheritsTestsTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/InheritsTestsTests.Test.verified.txt @@ -125,7 +125,9 @@ internal sealed class TUnit_TestProject_Bugs__1924_None_BaseClass_Test__int_Test switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -268,7 +270,9 @@ internal sealed class TUnit_TestProject_Bugs__1924_None_Tests_Test__int_TestSour switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -411,7 +415,9 @@ internal sealed class TUnit_TestProject_Bugs__1924_None_Tests2_Test__int_TestSou switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -554,7 +560,9 @@ internal sealed class TUnit_TestProject_Bugs__1924_None_Tests3_Test__int_TestSou switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/MatrixTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/MatrixTests.Test.verified.txt index f3e953ed52..ea365df2ae 100644 --- a/TUnit.Core.SourceGenerator.Tests/MatrixTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/MatrixTests.Test.verified.txt @@ -89,7 +89,9 @@ internal sealed class TUnit_TestProject_MatrixTests_MatrixTest_One__string_int_b switch (args.Length) { case 3: - return new global::System.Threading.Tasks.ValueTask(instance.MatrixTest_One(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.MatrixTest_One(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]))); + } default: throw new global::System.ArgumentException($"Expected exactly 3 arguments, but got {args.Length}"); } @@ -215,7 +217,9 @@ internal sealed class TUnit_TestProject_MatrixTests_MatrixTest_Two__int_int_int_ switch (args.Length) { case 4: - return new global::System.Threading.Tasks.ValueTask(instance.MatrixTest_Two(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.MatrixTest_Two(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]))); + } default: throw new global::System.ArgumentException($"Expected exactly 4 arguments, but got {args.Length}"); } @@ -334,7 +338,9 @@ internal sealed class TUnit_TestProject_MatrixTests_MatrixTest_Enum__int_TestEnu switch (args.Length) { case 3: - return new global::System.Threading.Tasks.ValueTask(instance.MatrixTest_Enum(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.MatrixTest_Enum(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]))); + } default: throw new global::System.ArgumentException($"Expected exactly 3 arguments, but got {args.Length}"); } @@ -446,7 +452,9 @@ internal sealed class TUnit_TestProject_MatrixTests_AutoGenerateBools__string_bo switch (args.Length) { case 2: - return new global::System.Threading.Tasks.ValueTask(instance.AutoGenerateBools(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.AutoGenerateBools(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + } default: throw new global::System.ArgumentException($"Expected exactly 2 arguments, but got {args.Length}"); } @@ -558,7 +566,9 @@ internal sealed class TUnit_TestProject_MatrixTests_AutoGenerateBools2__string_b switch (args.Length) { case 2: - return new global::System.Threading.Tasks.ValueTask(instance.AutoGenerateBools2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.AutoGenerateBools2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + } default: throw new global::System.ArgumentException($"Expected exactly 2 arguments, but got {args.Length}"); } @@ -670,7 +680,9 @@ internal sealed class TUnit_TestProject_MatrixTests_ImplicitConversion__OneOf_Te switch (args.Length) { case 2: - return new global::System.Threading.Tasks.ValueTask(instance.ImplicitConversion(TUnit.Core.Helpers.CastHelper.Cast>(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.ImplicitConversion(TUnit.Core.Helpers.CastHelper.Cast>(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + } default: throw new global::System.ArgumentException($"Expected exactly 2 arguments, but got {args.Length}"); } @@ -782,7 +794,9 @@ internal sealed class TUnit_TestProject_MatrixTests_ExcludingAutoGeneratedMatrix switch (args.Length) { case 2: - return new global::System.Threading.Tasks.ValueTask(instance.ExcludingAutoGeneratedMatrixValues(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.ExcludingAutoGeneratedMatrixValues(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + } default: throw new global::System.ArgumentException($"Expected exactly 2 arguments, but got {args.Length}"); } @@ -887,7 +901,9 @@ internal sealed class TUnit_TestProject_MatrixTests_Method1__int_TestSource : gl switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Method1(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Method1(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -992,7 +1008,9 @@ internal sealed class TUnit_TestProject_MatrixTests_Method2__int_TestSource : gl switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Method2(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Method2(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -1097,7 +1115,9 @@ internal sealed class TUnit_TestProject_MatrixTests_Method3__int_TestSource : gl switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Method3(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Method3(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -1202,7 +1222,9 @@ internal sealed class TUnit_TestProject_MatrixTests_Method4__int_TestSource : gl switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Method4(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Method4(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -1317,7 +1339,9 @@ internal sealed class TUnit_TestProject_MatrixTests_Exclusion__int_int_TestSourc switch (args.Length) { case 2: - return new global::System.Threading.Tasks.ValueTask(instance.Exclusion(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Exclusion(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]))); + } default: throw new global::System.ArgumentException($"Expected exactly 2 arguments, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/MethodDataSourceDrivenTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/MethodDataSourceDrivenTests.Test.verified.txt index 4ac45b08bb..beb11429e7 100644 --- a/TUnit.Core.SourceGenerator.Tests/MethodDataSourceDrivenTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/MethodDataSourceDrivenTests.Test.verified.txt @@ -86,8 +86,10 @@ internal sealed class TUnit_TestProject_MethodDataSourceDrivenTests_DataSource_M switch (args.Length) { case 1: - instance.DataSource_Method(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.DataSource_Method(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -203,8 +205,10 @@ internal sealed class TUnit_TestProject_MethodDataSourceDrivenTests_DataSource_M switch (args.Length) { case 1: - instance.DataSource_Method2(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.DataSource_Method2(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -320,8 +324,10 @@ internal sealed class TUnit_TestProject_MethodDataSourceDrivenTests_DataSource_M switch (args.Length) { case 1: - instance.DataSource_Method_WithAction(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.DataSource_Method_WithAction(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -452,8 +458,10 @@ internal sealed class TUnit_TestProject_MethodDataSourceDrivenTests_DataSource_M switch (args.Length) { case 1: - instance.DataSource_Method3(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.DataSource_Method3(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -611,8 +619,10 @@ internal sealed class TUnit_TestProject_MethodDataSourceDrivenTests_DataSource_M switch (args.Length) { case 1: - instance.DataSource_Method4(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.DataSource_Method4(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -728,8 +738,10 @@ internal sealed class TUnit_TestProject_MethodDataSourceDrivenTests_DataSource_W switch (args.Length) { case 1: - instance.DataSource_WithBaseReturn(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.DataSource_WithBaseReturn(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -855,7 +867,9 @@ internal sealed class TUnit_TestProject_MethodDataSourceDrivenTests_EnumerableFu switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.EnumerableFuncArrayTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.EnumerableFuncArrayTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/MethodDataSourceDrivenWithCancellationTokenTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/MethodDataSourceDrivenWithCancellationTokenTests.Test.verified.txt index a842404390..0a25276f19 100644 --- a/TUnit.Core.SourceGenerator.Tests/MethodDataSourceDrivenWithCancellationTokenTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/MethodDataSourceDrivenWithCancellationTokenTests.Test.verified.txt @@ -195,8 +195,10 @@ internal sealed class TUnit_TestProject_MethodDataSourceDrivenWithCancellationTo switch (args.Length) { case 1: - instance.MyTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]), context?.Execution.CancellationToken ?? System.Threading.CancellationToken.None); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.MyTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]), context?.Execution.CancellationToken ?? System.Threading.CancellationToken.None); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/NameOfArgumentTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/NameOfArgumentTests.Test.verified.txt index a89cf44b80..9bb78eda2d 100644 --- a/TUnit.Core.SourceGenerator.Tests/NameOfArgumentTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/NameOfArgumentTests.Test.verified.txt @@ -75,7 +75,9 @@ internal sealed class TUnit_TestProject_NameOfArgumentTests_TestName__string_Tes switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.TestName(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.TestName(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/NullableByteArgumentTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/NullableByteArgumentTests.Test.verified.txt index 7683db8709..60b1e9ef0e 100644 --- a/TUnit.Core.SourceGenerator.Tests/NullableByteArgumentTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/NullableByteArgumentTests.Test.verified.txt @@ -76,8 +76,10 @@ internal sealed class TUnit_TestProject_NullableByteArgumentTests_Test__byte__Te switch (args.Length) { case 1: - instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -190,8 +192,10 @@ internal sealed class TUnit_TestProject_NullableByteArgumentTests_Test2__byte_by switch (args.Length) { case 2: - instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 2 arguments, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/NumberArgumentTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/NumberArgumentTests.Test.verified.txt index f953755712..ba3b1e1df9 100644 --- a/TUnit.Core.SourceGenerator.Tests/NumberArgumentTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/NumberArgumentTests.Test.verified.txt @@ -75,8 +75,10 @@ internal sealed class TUnit_TestProject_NumberArgumentTests_Int__int_TestSource switch (args.Length) { case 1: - instance.Int(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Int(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -181,8 +183,10 @@ internal sealed class TUnit_TestProject_NumberArgumentTests_Double__double_TestS switch (args.Length) { case 1: - instance.Double(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Double(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -287,8 +291,10 @@ internal sealed class TUnit_TestProject_NumberArgumentTests_Float__float_TestSou switch (args.Length) { case 1: - instance.Float(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Float(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -393,8 +399,10 @@ internal sealed class TUnit_TestProject_NumberArgumentTests_Long__long_TestSourc switch (args.Length) { case 1: - instance.Long(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Long(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -499,8 +507,10 @@ internal sealed class TUnit_TestProject_NumberArgumentTests_ULong__ulong_TestSou switch (args.Length) { case 1: - instance.ULong(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.ULong(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -605,8 +615,10 @@ internal sealed class TUnit_TestProject_NumberArgumentTests_UInt__uint_TestSourc switch (args.Length) { case 1: - instance.UInt(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.UInt(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/NumberArgumentTests.TestDE.verified.txt b/TUnit.Core.SourceGenerator.Tests/NumberArgumentTests.TestDE.verified.txt index f953755712..ba3b1e1df9 100644 --- a/TUnit.Core.SourceGenerator.Tests/NumberArgumentTests.TestDE.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/NumberArgumentTests.TestDE.verified.txt @@ -75,8 +75,10 @@ internal sealed class TUnit_TestProject_NumberArgumentTests_Int__int_TestSource switch (args.Length) { case 1: - instance.Int(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Int(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -181,8 +183,10 @@ internal sealed class TUnit_TestProject_NumberArgumentTests_Double__double_TestS switch (args.Length) { case 1: - instance.Double(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Double(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -287,8 +291,10 @@ internal sealed class TUnit_TestProject_NumberArgumentTests_Float__float_TestSou switch (args.Length) { case 1: - instance.Float(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Float(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -393,8 +399,10 @@ internal sealed class TUnit_TestProject_NumberArgumentTests_Long__long_TestSourc switch (args.Length) { case 1: - instance.Long(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Long(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -499,8 +507,10 @@ internal sealed class TUnit_TestProject_NumberArgumentTests_ULong__ulong_TestSou switch (args.Length) { case 1: - instance.ULong(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.ULong(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -605,8 +615,10 @@ internal sealed class TUnit_TestProject_NumberArgumentTests_UInt__uint_TestSourc switch (args.Length) { case 1: - instance.UInt(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.UInt(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/StringArgumentTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/StringArgumentTests.Test.verified.txt index ddf79f8751..6e12c5b360 100644 --- a/TUnit.Core.SourceGenerator.Tests/StringArgumentTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/StringArgumentTests.Test.verified.txt @@ -86,8 +86,10 @@ internal sealed class TUnit_TestProject_StringArgumentTests_Normal__string_TestS switch (args.Length) { case 1: - instance.Normal(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Normal(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -199,8 +201,10 @@ internal sealed class TUnit_TestProject_StringArgumentTests_Nullable__string__Te switch (args.Length) { case 1: - instance.Nullable(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Nullable(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/Tests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/Tests.Test.verified.txt index ae338ab8db..b14aa06aac 100644 --- a/TUnit.Core.SourceGenerator.Tests/Tests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/Tests.Test.verified.txt @@ -87,7 +87,9 @@ internal sealed class TUnit_TestProject_Bugs__1304_Tests_TryParse_InvalidString_ switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.TryParse_InvalidString_ReturnsFailure(TUnit.Core.Helpers.CastHelper.Cast(args[0]), context?.Execution.CancellationToken ?? System.Threading.CancellationToken.None)); + { + return new global::System.Threading.Tasks.ValueTask(instance.TryParse_InvalidString_ReturnsFailure(TUnit.Core.Helpers.CastHelper.Cast(args[0]), context?.Execution.CancellationToken ?? System.Threading.CancellationToken.None)); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -196,7 +198,9 @@ internal sealed class TUnit_TestProject_Bugs__1304_Tests_Parse_InvalidString_Thr switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Parse_InvalidString_ThrowsException(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Parse_InvalidString_ThrowsException(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -302,7 +306,9 @@ internal sealed class TUnit_TestProject_Bugs__1304_Tests_TryParse_ValidString_Re switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.TryParse_ValidString_ReturnsAccountId(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.TryParse_ValidString_ReturnsAccountId(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/Tests1538.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/Tests1538.Test.verified.txt index f58a3c0aac..8851e6d423 100644 --- a/TUnit.Core.SourceGenerator.Tests/Tests1538.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/Tests1538.Test.verified.txt @@ -145,8 +145,10 @@ internal sealed class TUnit_TestProject_Bugs__1538_Tests_Eight_Args__bool_string switch (args.Length) { case 8: - instance.Eight_Args(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]), TUnit.Core.Helpers.CastHelper.Cast(args[5]), TUnit.Core.Helpers.CastHelper.Cast(args[6]), TUnit.Core.Helpers.CastHelper.Cast(args[7])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Eight_Args(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]), TUnit.Core.Helpers.CastHelper.Cast(args[5]), TUnit.Core.Helpers.CastHelper.Cast(args[6]), TUnit.Core.Helpers.CastHelper.Cast(args[7])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 8 arguments, but got {args.Length}"); } @@ -377,8 +379,10 @@ internal sealed class TUnit_TestProject_Bugs__1538_Tests_SixteenArgs__bool_strin switch (args.Length) { case 16: - instance.SixteenArgs(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]), TUnit.Core.Helpers.CastHelper.Cast(args[5]), TUnit.Core.Helpers.CastHelper.Cast(args[6]), TUnit.Core.Helpers.CastHelper.Cast(args[7]), TUnit.Core.Helpers.CastHelper.Cast(args[8]), TUnit.Core.Helpers.CastHelper.Cast(args[9]), TUnit.Core.Helpers.CastHelper.Cast(args[10]), TUnit.Core.Helpers.CastHelper.Cast(args[11]), TUnit.Core.Helpers.CastHelper.Cast(args[12]), TUnit.Core.Helpers.CastHelper.Cast(args[13]), TUnit.Core.Helpers.CastHelper.Cast(args[14]), TUnit.Core.Helpers.CastHelper.Cast(args[15])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.SixteenArgs(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]), TUnit.Core.Helpers.CastHelper.Cast(args[5]), TUnit.Core.Helpers.CastHelper.Cast(args[6]), TUnit.Core.Helpers.CastHelper.Cast(args[7]), TUnit.Core.Helpers.CastHelper.Cast(args[8]), TUnit.Core.Helpers.CastHelper.Cast(args[9]), TUnit.Core.Helpers.CastHelper.Cast(args[10]), TUnit.Core.Helpers.CastHelper.Cast(args[11]), TUnit.Core.Helpers.CastHelper.Cast(args[12]), TUnit.Core.Helpers.CastHelper.Cast(args[13]), TUnit.Core.Helpers.CastHelper.Cast(args[14]), TUnit.Core.Helpers.CastHelper.Cast(args[15])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 16 arguments, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/Tests1603.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/Tests1603.Test.verified.txt index 3a4cae1c70..1dbf27639e 100644 --- a/TUnit.Core.SourceGenerator.Tests/Tests1603.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/Tests1603.Test.verified.txt @@ -75,7 +75,9 @@ internal sealed class TUnit_TestProject_Bugs__1603_Tests_Casted_Integer_To_Short switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Casted_Integer_To_Short_Converts(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Casted_Integer_To_Short_Converts(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -180,7 +182,9 @@ internal sealed class TUnit_TestProject_Bugs__1603_Tests_Integer_To_Short_Conver switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Integer_To_Short_Converts(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Integer_To_Short_Converts(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/Tests1692.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/Tests1692.Test.verified.txt index dc434877b6..e33251f183 100644 --- a/TUnit.Core.SourceGenerator.Tests/Tests1692.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/Tests1692.Test.verified.txt @@ -76,7 +76,9 @@ internal sealed class TUnit_TestProject_Bugs__1692_Tests_NullTest__string__TestS switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.NullTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.NullTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/Tests1821.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/Tests1821.Test.verified.txt index 13a93df557..2c961017ad 100644 --- a/TUnit.Core.SourceGenerator.Tests/Tests1821.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/Tests1821.Test.verified.txt @@ -91,7 +91,9 @@ internal sealed class TUnit_TestProject_Bugs__1821_Tests_MethodDataSource__strin switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.MethodDataSource(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.MethodDataSource(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -212,7 +214,9 @@ internal sealed class TUnit_TestProject_Bugs__1821_Tests_MatrixDataSource__strin switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.MatrixDataSource(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.MatrixDataSource(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/Tests1889.Test.DotNet10_0.verified.txt b/TUnit.Core.SourceGenerator.Tests/Tests1889.Test.DotNet10_0.verified.txt index 65d6193dd3..d867b43a61 100644 --- a/TUnit.Core.SourceGenerator.Tests/Tests1889.Test.DotNet10_0.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/Tests1889.Test.DotNet10_0.verified.txt @@ -165,8 +165,10 @@ internal sealed class TUnit_TestProject_Bugs__1889_DerivedTest_Test2__bool_TestS switch (args.Length) { case 1: - instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -273,8 +275,10 @@ internal sealed class TUnit_TestProject_Bugs__1889_DerivedTest_Test3__bool_TestS switch (args.Length) { case 1: - instance.Test3(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test3(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/Tests1889.Test.DotNet8_0.verified.txt b/TUnit.Core.SourceGenerator.Tests/Tests1889.Test.DotNet8_0.verified.txt index 65d6193dd3..d867b43a61 100644 --- a/TUnit.Core.SourceGenerator.Tests/Tests1889.Test.DotNet8_0.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/Tests1889.Test.DotNet8_0.verified.txt @@ -165,8 +165,10 @@ internal sealed class TUnit_TestProject_Bugs__1889_DerivedTest_Test2__bool_TestS switch (args.Length) { case 1: - instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -273,8 +275,10 @@ internal sealed class TUnit_TestProject_Bugs__1889_DerivedTest_Test3__bool_TestS switch (args.Length) { case 1: - instance.Test3(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test3(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/Tests1889.Test.DotNet9_0.verified.txt b/TUnit.Core.SourceGenerator.Tests/Tests1889.Test.DotNet9_0.verified.txt index 65d6193dd3..d867b43a61 100644 --- a/TUnit.Core.SourceGenerator.Tests/Tests1889.Test.DotNet9_0.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/Tests1889.Test.DotNet9_0.verified.txt @@ -165,8 +165,10 @@ internal sealed class TUnit_TestProject_Bugs__1889_DerivedTest_Test2__bool_TestS switch (args.Length) { case 1: - instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -273,8 +275,10 @@ internal sealed class TUnit_TestProject_Bugs__1889_DerivedTest_Test3__bool_TestS switch (args.Length) { case 1: - instance.Test3(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test3(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/Tests1889.Test.Net4_7.verified.txt b/TUnit.Core.SourceGenerator.Tests/Tests1889.Test.Net4_7.verified.txt index aabb7073ca..eab0c4bca0 100644 --- a/TUnit.Core.SourceGenerator.Tests/Tests1889.Test.Net4_7.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/Tests1889.Test.Net4_7.verified.txt @@ -165,8 +165,10 @@ internal sealed class TUnit_TestProject_Bugs__1889_DerivedTest_Test2__bool_TestS switch (args.Length) { case 1: - instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -273,8 +275,10 @@ internal sealed class TUnit_TestProject_Bugs__1889_DerivedTest_Test3__bool_TestS switch (args.Length) { case 1: - instance.Test3(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test3(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/Tests2083.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/Tests2083.Test.verified.txt index 7106ee7e81..38dbc9f180 100644 --- a/TUnit.Core.SourceGenerator.Tests/Tests2083.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/Tests2083.Test.verified.txt @@ -80,8 +80,10 @@ internal sealed class TUnit_TestProject_Bugs__2083_Tests_MyTest__long_TestSource switch (args.Length) { case 1: - instance.MyTest(TUnit.Core.Helpers.CastHelper.Cast(args[0])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.MyTest(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/Tests2085.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/Tests2085.Test.verified.txt index aa612dac45..c5d52626cf 100644 --- a/TUnit.Core.SourceGenerator.Tests/Tests2085.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/Tests2085.Test.verified.txt @@ -75,7 +75,9 @@ internal sealed class TUnit_TestProject_Bugs__2085_Tests_Double_SpecialConsts__d switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Double_SpecialConsts(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Double_SpecialConsts(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -180,7 +182,9 @@ internal sealed class TUnit_TestProject_Bugs__2085_Tests_Float_SpecialConsts__fl switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.Float_SpecialConsts(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + { + return new global::System.Threading.Tasks.ValueTask(instance.Float_SpecialConsts(TUnit.Core.Helpers.CastHelper.Cast(args[0]))); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/Tests2112.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/Tests2112.Test.verified.txt index 8d2e037a83..269c08b65f 100644 --- a/TUnit.Core.SourceGenerator.Tests/Tests2112.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/Tests2112.Test.verified.txt @@ -83,26 +83,40 @@ internal sealed class TUnit_TestProject_Bugs__2112_Tests_Test__int_long___TestSo switch (args.Length) { case 1: - instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[0]); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[0]); + return default(global::System.Threading.Tasks.ValueTask); + } case 2: - instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), (args[1] is null ? null : args[1] is long[] arr ? arr : new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]) })); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), (args[1] is null ? null : args[1] is long[] arr ? arr : new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]) })); + return default(global::System.Threading.Tasks.ValueTask); + } case 3: - instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]) }); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]) }); + return default(global::System.Threading.Tasks.ValueTask); + } case 4: - instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]) }); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]) }); + return default(global::System.Threading.Tasks.ValueTask); + } case 5: - instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]) }); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]) }); + return default(global::System.Threading.Tasks.ValueTask); + } case 6: - instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]), TUnit.Core.Helpers.CastHelper.Cast(args[5]) }); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]), TUnit.Core.Helpers.CastHelper.Cast(args[5]) }); + return default(global::System.Threading.Tasks.ValueTask); + } case 7: - instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]), TUnit.Core.Helpers.CastHelper.Cast(args[5]), TUnit.Core.Helpers.CastHelper.Cast(args[6]) }); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]), TUnit.Core.Helpers.CastHelper.Cast(args[5]), TUnit.Core.Helpers.CastHelper.Cast(args[6]) }); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected between 1 and 2 arguments, but got {args.Length}"); } @@ -215,26 +229,40 @@ internal sealed class TUnit_TestProject_Bugs__2112_Tests_Test2__int_long___TestS switch (args.Length) { case 1: - instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[0]); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[0]); + return default(global::System.Threading.Tasks.ValueTask); + } case 2: - instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), (args[1] is null ? null : args[1] is long[] arr ? arr : new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]) })); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), (args[1] is null ? null : args[1] is long[] arr ? arr : new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]) })); + return default(global::System.Threading.Tasks.ValueTask); + } case 3: - instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]) }); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]) }); + return default(global::System.Threading.Tasks.ValueTask); + } case 4: - instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]) }); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]) }); + return default(global::System.Threading.Tasks.ValueTask); + } case 5: - instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]) }); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]) }); + return default(global::System.Threading.Tasks.ValueTask); + } case 6: - instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]), TUnit.Core.Helpers.CastHelper.Cast(args[5]) }); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]), TUnit.Core.Helpers.CastHelper.Cast(args[5]) }); + return default(global::System.Threading.Tasks.ValueTask); + } case 7: - instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]), TUnit.Core.Helpers.CastHelper.Cast(args[5]), TUnit.Core.Helpers.CastHelper.Cast(args[6]) }); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.Test2(TUnit.Core.Helpers.CastHelper.Cast(args[0]), new long[] { TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2]), TUnit.Core.Helpers.CastHelper.Cast(args[3]), TUnit.Core.Helpers.CastHelper.Cast(args[4]), TUnit.Core.Helpers.CastHelper.Cast(args[5]), TUnit.Core.Helpers.CastHelper.Cast(args[6]) }); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected between 1 and 2 arguments, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/TestsBase.cs b/TUnit.Core.SourceGenerator.Tests/TestsBase.cs index 161a01beed..98e003ad13 100644 --- a/TUnit.Core.SourceGenerator.Tests/TestsBase.cs +++ b/TUnit.Core.SourceGenerator.Tests/TestsBase.cs @@ -43,7 +43,7 @@ public Task RunTest(string inputFile, Func assertions) public async Task RunTest(string inputFile, RunTestOptions runTestOptions, Func assertions) { #if NET - var source = await FilePolyfill.ReadAllTextAsync(inputFile); + var source = await File.ReadAllTextAsync(inputFile); #else var source = File.ReadAllText(inputFile); #endif @@ -86,7 +86,7 @@ namespace System.Diagnostics.CodeAnalysis; public class UnconditionalSuppressMessageAttribute : Attribute; """, #if NET - ..await Task.WhenAll(runTestOptions.AdditionalFiles.Select(x => FilePolyfill.ReadAllTextAsync(x))), + ..await Task.WhenAll(runTestOptions.AdditionalFiles.Select(x => File.ReadAllTextAsync(x))), #else ..runTestOptions.AdditionalFiles.Select(x => File.ReadAllText(x)), #endif @@ -172,8 +172,8 @@ Have you added required references and additional files? { verifyTask = verifyTask.OnVerifyMismatch(async (pair, message, verify) => { - var received = await FilePolyfill.ReadAllTextAsync(pair.ReceivedPath); - var verified = await FilePolyfill.ReadAllTextAsync(pair.VerifiedPath); + var received = await File.ReadAllTextAsync(pair.ReceivedPath); + var verified = await File.ReadAllTextAsync(pair.VerifiedPath); // Better diff message since original one is too large await Assert.That(Scrub(received)).IsEqualTo(Scrub(verified)); diff --git a/TUnit.Core.SourceGenerator.Tests/TimeoutCancellationTokenTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/TimeoutCancellationTokenTests.Test.verified.txt index 5c46854523..b4e8b9f589 100644 --- a/TUnit.Core.SourceGenerator.Tests/TimeoutCancellationTokenTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/TimeoutCancellationTokenTests.Test.verified.txt @@ -527,7 +527,9 @@ internal sealed class TUnit_TestProject_TimeoutCancellationTokenTests_DataTest__ switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.DataTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]), context?.Execution.CancellationToken ?? System.Threading.CancellationToken.None)); + { + return new global::System.Threading.Tasks.ValueTask(instance.DataTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]), context?.Execution.CancellationToken ?? System.Threading.CancellationToken.None)); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -700,7 +702,9 @@ internal sealed class TUnit_TestProject_TimeoutCancellationTokenTests_DataSource switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.DataSourceTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]), context?.Execution.CancellationToken ?? System.Threading.CancellationToken.None)); + { + return new global::System.Threading.Tasks.ValueTask(instance.DataSourceTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]), context?.Execution.CancellationToken ?? System.Threading.CancellationToken.None)); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -853,7 +857,9 @@ internal sealed class TUnit_TestProject_TimeoutCancellationTokenTests_MatrixTest switch (args.Length) { case 1: - return new global::System.Threading.Tasks.ValueTask(instance.MatrixTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]), context?.Execution.CancellationToken ?? System.Threading.CancellationToken.None)); + { + return new global::System.Threading.Tasks.ValueTask(instance.MatrixTest(TUnit.Core.Helpers.CastHelper.Cast(args[0]), context?.Execution.CancellationToken ?? System.Threading.CancellationToken.None)); + } default: throw new global::System.ArgumentException($"Expected exactly 1 argument, but got {args.Length}"); } @@ -877,3 +883,273 @@ internal static class TUnit_TestProject_TimeoutCancellationTokenTests_MatrixTest global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.TimeoutCancellationTokenTests), new TUnit_TestProject_TimeoutCancellationTokenTests_MatrixTest__int_CancellationToken_TestSource()); } } + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable + +#nullable enable +namespace TUnit.Generated; +internal sealed class TUnit_TestProject_TimeoutDoesNotFireTests_QuickTestDoesNotTimeout__CancellationToken_TestSource : global::TUnit.Core.Interfaces.SourceGenerator.ITestSource +{ + public async global::System.Collections.Generic.IAsyncEnumerable GetTestsAsync(string testSessionId, [global::System.Runtime.CompilerServices.EnumeratorCancellation] global::System.Threading.CancellationToken cancellationToken = default) + { + var metadata = new global::TUnit.Core.TestMetadata + { + TestName = "QuickTestDoesNotTimeout", + TestClassType = typeof(global::TUnit.TestProject.TimeoutDoesNotFireTests), + TestMethodName = "QuickTestDoesNotTimeout", + Dependencies = global::System.Array.Empty(), + AttributeFactory = static () => + [ + new global::TUnit.Core.TestAttribute(), + new global::TUnit.Core.MethodDataSourceAttribute("DataSource"), + new global::TUnit.Core.TimeoutAttribute(30_000), + new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Pass), + new global::TUnit.Core.CategoryAttribute("Timeout Cancellation Token Tests") + ], + DataSources = global::System.Array.Empty(), + ClassDataSources = new global::TUnit.Core.IDataSourceAttribute[] + { + new global::TUnit.Core.MethodDataSourceAttribute("DataSource") + { + Factory = (dataGeneratorMetadata) => + { + async global::System.Collections.Generic.IAsyncEnumerable>> Factory() + { + var result = global::TUnit.TestProject.TimeoutDoesNotFireTests.DataSource(); + if (result is global::System.Collections.IEnumerable enumerable && !(result is string)) + { + foreach (var item in enumerable) + { + yield return () => global::System.Threading.Tasks.Task.FromResult(global::TUnit.Core.Helpers.DataSourceHelpers.ToObjectArray(item)); + } + } + else + { + yield return () => global::System.Threading.Tasks.Task.FromResult(global::TUnit.Core.Helpers.DataSourceHelpers.ToObjectArray(result)); + } + } + return Factory(); + } + }, + }, + PropertyDataSources = global::System.Array.Empty(), + PropertyInjections = global::System.Array.Empty(), + InheritanceDepth = 0, + FilePath = @"", + LineNumber = 86, + MethodMetadata = new global::TUnit.Core.MethodMetadata + { + Type = typeof(global::TUnit.TestProject.TimeoutDoesNotFireTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.TimeoutDoesNotFireTests)), + Name = "QuickTestDoesNotTimeout", + GenericTypeCount = 0, + ReturnType = typeof(global::System.Threading.Tasks.Task), + ReturnTypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::System.Threading.Tasks.Task)), + Parameters = new global::TUnit.Core.ParameterMetadata[] + { + new global::TUnit.Core.ParameterMetadata(typeof(global::System.Threading.CancellationToken)) + { + Name = "cancellationToken", + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::System.Threading.CancellationToken)), + IsNullable = false, + ReflectionInfo = typeof(global::TUnit.TestProject.TimeoutDoesNotFireTests).GetMethod("QuickTestDoesNotTimeout", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance, null, new global::System.Type[] { typeof(global::System.Threading.CancellationToken) }, null)!.GetParameters()[0] + } + }, + Class = global::TUnit.Core.ClassMetadata.GetOrAdd("TestsBase`1:global::TUnit.TestProject.TimeoutDoesNotFireTests", static () => + { + var classMetadata = new global::TUnit.Core.ClassMetadata + { + Type = typeof(global::TUnit.TestProject.TimeoutDoesNotFireTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.TimeoutDoesNotFireTests)), + Name = "TimeoutDoesNotFireTests", + Namespace = "TUnit.TestProject", + Assembly = global::TUnit.Core.AssemblyMetadata.GetOrAdd("TestsBase`1", static () => new global::TUnit.Core.AssemblyMetadata { Name = "TestsBase`1" }), + Parameters = new global::TUnit.Core.ParameterMetadata[] + { + new global::TUnit.Core.ParameterMetadata(typeof(int)) + { + Name = "value", + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(int)), + IsNullable = false, + ReflectionInfo = typeof(global::TUnit.TestProject.TimeoutDoesNotFireTests).GetConstructor(new global::System.Type[] { typeof(int) })!.GetParameters()[0] + } + }, + Properties = global::System.Array.Empty(), + Parent = null + }; + foreach (var prop in classMetadata.Properties) + { + prop.ClassMetadata = classMetadata; + prop.ContainingTypeMetadata = classMetadata; + } + return classMetadata; + }) + }, + InstanceFactory = (typeArgs, args) => + { + return new global::TUnit.TestProject.TimeoutDoesNotFireTests(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + }, + InvokeTypedTest = static (instance, args, cancellationToken) => + { + try + { + return new global::System.Threading.Tasks.ValueTask(instance.QuickTestDoesNotTimeout(cancellationToken)); + } + catch (global::System.Exception ex) + { + return new global::System.Threading.Tasks.ValueTask(global::System.Threading.Tasks.Task.FromException(ex)); + } + }, + }; + metadata.UseRuntimeDataGeneration(testSessionId); + yield return metadata; + yield break; + } +} +internal static class TUnit_TestProject_TimeoutDoesNotFireTests_QuickTestDoesNotTimeout__CancellationToken_ModuleInitializer +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + public static void Initialize() + { + global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.TimeoutDoesNotFireTests), new TUnit_TestProject_TimeoutDoesNotFireTests_QuickTestDoesNotTimeout__CancellationToken_TestSource()); + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable + +#nullable enable +namespace TUnit.Generated; +internal sealed class TUnit_TestProject_CancellationTokenTriggeredTests_CancellationTokenIsTriggered__CancellationToken_TestSource : global::TUnit.Core.Interfaces.SourceGenerator.ITestSource +{ + public async global::System.Collections.Generic.IAsyncEnumerable GetTestsAsync(string testSessionId, [global::System.Runtime.CompilerServices.EnumeratorCancellation] global::System.Threading.CancellationToken cancellationToken = default) + { + var metadata = new global::TUnit.Core.TestMetadata + { + TestName = "CancellationTokenIsTriggered", + TestClassType = typeof(global::TUnit.TestProject.CancellationTokenTriggeredTests), + TestMethodName = "CancellationTokenIsTriggered", + Dependencies = global::System.Array.Empty(), + AttributeFactory = static () => + [ + new global::TUnit.Core.TestAttribute(), + new global::TUnit.Core.MethodDataSourceAttribute("DataSource"), + new global::TUnit.Core.TimeoutAttribute(5_000), + new global::TUnit.TestProject.Attributes.EngineTest(global::TUnit.TestProject.Attributes.ExpectedResult.Failure), + new global::TUnit.Core.CategoryAttribute("Timeout Cancellation Token Tests") + ], + DataSources = global::System.Array.Empty(), + ClassDataSources = new global::TUnit.Core.IDataSourceAttribute[] + { + new global::TUnit.Core.MethodDataSourceAttribute("DataSource") + { + Factory = (dataGeneratorMetadata) => + { + async global::System.Collections.Generic.IAsyncEnumerable>> Factory() + { + var result = global::TUnit.TestProject.CancellationTokenTriggeredTests.DataSource(); + if (result is global::System.Collections.IEnumerable enumerable && !(result is string)) + { + foreach (var item in enumerable) + { + yield return () => global::System.Threading.Tasks.Task.FromResult(global::TUnit.Core.Helpers.DataSourceHelpers.ToObjectArray(item)); + } + } + else + { + yield return () => global::System.Threading.Tasks.Task.FromResult(global::TUnit.Core.Helpers.DataSourceHelpers.ToObjectArray(result)); + } + } + return Factory(); + } + }, + }, + PropertyDataSources = global::System.Array.Empty(), + PropertyInjections = global::System.Array.Empty(), + InheritanceDepth = 0, + FilePath = @"", + LineNumber = 106, + MethodMetadata = new global::TUnit.Core.MethodMetadata + { + Type = typeof(global::TUnit.TestProject.CancellationTokenTriggeredTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.CancellationTokenTriggeredTests)), + Name = "CancellationTokenIsTriggered", + GenericTypeCount = 0, + ReturnType = typeof(global::System.Threading.Tasks.Task), + ReturnTypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::System.Threading.Tasks.Task)), + Parameters = new global::TUnit.Core.ParameterMetadata[] + { + new global::TUnit.Core.ParameterMetadata(typeof(global::System.Threading.CancellationToken)) + { + Name = "cancellationToken", + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::System.Threading.CancellationToken)), + IsNullable = false, + ReflectionInfo = typeof(global::TUnit.TestProject.CancellationTokenTriggeredTests).GetMethod("CancellationTokenIsTriggered", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance, null, new global::System.Type[] { typeof(global::System.Threading.CancellationToken) }, null)!.GetParameters()[0] + } + }, + Class = global::TUnit.Core.ClassMetadata.GetOrAdd("TestsBase`1:global::TUnit.TestProject.CancellationTokenTriggeredTests", static () => + { + var classMetadata = new global::TUnit.Core.ClassMetadata + { + Type = typeof(global::TUnit.TestProject.CancellationTokenTriggeredTests), + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(global::TUnit.TestProject.CancellationTokenTriggeredTests)), + Name = "CancellationTokenTriggeredTests", + Namespace = "TUnit.TestProject", + Assembly = global::TUnit.Core.AssemblyMetadata.GetOrAdd("TestsBase`1", static () => new global::TUnit.Core.AssemblyMetadata { Name = "TestsBase`1" }), + Parameters = new global::TUnit.Core.ParameterMetadata[] + { + new global::TUnit.Core.ParameterMetadata(typeof(int)) + { + Name = "value", + TypeInfo = new global::TUnit.Core.ConcreteType(typeof(int)), + IsNullable = false, + ReflectionInfo = typeof(global::TUnit.TestProject.CancellationTokenTriggeredTests).GetConstructor(new global::System.Type[] { typeof(int) })!.GetParameters()[0] + } + }, + Properties = global::System.Array.Empty(), + Parent = null + }; + foreach (var prop in classMetadata.Properties) + { + prop.ClassMetadata = classMetadata; + prop.ContainingTypeMetadata = classMetadata; + } + return classMetadata; + }) + }, + InstanceFactory = (typeArgs, args) => + { + return new global::TUnit.TestProject.CancellationTokenTriggeredTests(TUnit.Core.Helpers.CastHelper.Cast(args[0])); + }, + InvokeTypedTest = static (instance, args, cancellationToken) => + { + try + { + return new global::System.Threading.Tasks.ValueTask(instance.CancellationTokenIsTriggered(cancellationToken)); + } + catch (global::System.Exception ex) + { + return new global::System.Threading.Tasks.ValueTask(global::System.Threading.Tasks.Task.FromException(ex)); + } + }, + }; + metadata.UseRuntimeDataGeneration(testSessionId); + yield return metadata; + yield break; + } +} +internal static class TUnit_TestProject_CancellationTokenTriggeredTests_CancellationTokenIsTriggered__CancellationToken_ModuleInitializer +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + public static void Initialize() + { + global::TUnit.Core.SourceRegistrar.Register(typeof(global::TUnit.TestProject.CancellationTokenTriggeredTests), new TUnit_TestProject_CancellationTokenTriggeredTests_CancellationTokenIsTriggered__CancellationToken_TestSource()); + } +} diff --git a/TUnit.Core.SourceGenerator.Tests/TupleDataSourceDrivenTests.Test.verified.txt b/TUnit.Core.SourceGenerator.Tests/TupleDataSourceDrivenTests.Test.verified.txt index 11251b1886..581ace613b 100644 --- a/TUnit.Core.SourceGenerator.Tests/TupleDataSourceDrivenTests.Test.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/TupleDataSourceDrivenTests.Test.verified.txt @@ -100,8 +100,10 @@ internal sealed class TUnit_TestProject_TupleDataSourceDrivenTests_DataSource_Tu switch (args.Length) { case 3: - instance.DataSource_TupleMethod(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.DataSource_TupleMethod(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 3 arguments, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/UnifiedReflectionFreeTests.Test_AotSafeDataSourceFactories.verified.txt b/TUnit.Core.SourceGenerator.Tests/UnifiedReflectionFreeTests.Test_AotSafeDataSourceFactories.verified.txt index c8205e4f6e..7190aae3f4 100644 --- a/TUnit.Core.SourceGenerator.Tests/UnifiedReflectionFreeTests.Test_AotSafeDataSourceFactories.verified.txt +++ b/TUnit.Core.SourceGenerator.Tests/UnifiedReflectionFreeTests.Test_AotSafeDataSourceFactories.verified.txt @@ -89,8 +89,10 @@ internal sealed class TUnit_TestProject_AotDataSourceTest_TestWithDataSource__in switch (args.Length) { case 3: - instance.TestWithDataSource(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2])); - return default(global::System.Threading.Tasks.ValueTask); + { + instance.TestWithDataSource(TUnit.Core.Helpers.CastHelper.Cast(args[0]), TUnit.Core.Helpers.CastHelper.Cast(args[1]), TUnit.Core.Helpers.CastHelper.Cast(args[2])); + return default(global::System.Threading.Tasks.ValueTask); + } default: throw new global::System.ArgumentException($"Expected exactly 3 arguments, but got {args.Length}"); } diff --git a/TUnit.Core.SourceGenerator.Tests/UnifiedReflectionFreeTests.cs b/TUnit.Core.SourceGenerator.Tests/UnifiedReflectionFreeTests.cs index 3a039c557d..6182181767 100644 --- a/TUnit.Core.SourceGenerator.Tests/UnifiedReflectionFreeTests.cs +++ b/TUnit.Core.SourceGenerator.Tests/UnifiedReflectionFreeTests.cs @@ -95,7 +95,7 @@ public void TestWithConfiguration() private async Task RunTestWithInlineSource(string source) { var tempFile = Path.GetTempFileName() + ".cs"; - await FilePolyfill.WriteAllTextAsync(tempFile, source); + await File.WriteAllTextAsync(tempFile, source); try { diff --git a/TUnit.Core.SourceGenerator.Tests/Verify.cs b/TUnit.Core.SourceGenerator.Tests/Verify.cs index d1db167879..d15b2ac87a 100644 --- a/TUnit.Core.SourceGenerator.Tests/Verify.cs +++ b/TUnit.Core.SourceGenerator.Tests/Verify.cs @@ -157,15 +157,15 @@ public async Task ToTask() if (!File.Exists(_verifiedPath)) { - await FilePolyfill.WriteAllTextAsync(_receivedPath, NormalizeNewline(final)); + await File.WriteAllTextAsync(_receivedPath, NormalizeNewline(final)); throw new InvalidOperationException($"No verified file found for '{name}'."); } - var approved = await FilePolyfill.ReadAllTextAsync(_verifiedPath); + var approved = await File.ReadAllTextAsync(_verifiedPath); if (!string.Equals(NormalizeNewline(final), NormalizeNewline(approved), StringComparison.Ordinal)) { - await FilePolyfill.WriteAllTextAsync(_receivedPath, NormalizeNewline(final)); + await File.WriteAllTextAsync(_receivedPath, NormalizeNewline(final)); if (_onVerifyMismatch != null) { diff --git a/TUnit.Core.SourceGenerator/CodeGenerators/Helpers/InstanceFactoryGenerator.cs b/TUnit.Core.SourceGenerator/CodeGenerators/Helpers/InstanceFactoryGenerator.cs index 38fb99fadc..1d6e6cc0bb 100644 --- a/TUnit.Core.SourceGenerator/CodeGenerators/Helpers/InstanceFactoryGenerator.cs +++ b/TUnit.Core.SourceGenerator/CodeGenerators/Helpers/InstanceFactoryGenerator.cs @@ -6,6 +6,37 @@ namespace TUnit.Core.SourceGenerator.CodeGenerators.Helpers; public static class InstanceFactoryGenerator { + /// + /// Generates code to create an instance of a type with proper required property handling. + /// This handles required properties that don't have data sources by initializing them with defaults. + /// + public static void GenerateInstanceCreation(CodeWriter writer, ITypeSymbol typeSymbol, string variableName) + { + var className = typeSymbol.GloballyQualified(); + // Use GetAllRequiredProperties because even properties with data sources need to be + // initialized in the object initializer to satisfy C#'s required modifier constraint. + // The actual values will be populated by the data sources at runtime. + var requiredProperties = RequiredPropertyHelper.GetAllRequiredProperties(typeSymbol).ToArray(); + + if (requiredProperties.Length == 0) + { + writer.AppendLine($"{variableName} = new {className}();"); + } + else + { + writer.AppendLine($"{variableName} = new {className}()"); + writer.AppendLine("{"); + writer.Indent(); + foreach (var property in requiredProperties) + { + var defaultValue = RequiredPropertyHelper.GetDefaultValueForType(property.Type); + writer.AppendLine($"{property.Name} = {defaultValue},"); + } + writer.Unindent(); + writer.AppendLine("};"); + } + } + public static void GenerateInstanceFactory(CodeWriter writer, ITypeSymbol typeSymbol) { GenerateInstanceFactory(writer, typeSymbol, null); diff --git a/TUnit.Core.SourceGenerator/Generators/PropertyInjectionSourceGenerator.cs b/TUnit.Core.SourceGenerator/Generators/PropertyInjectionSourceGenerator.cs index 6a20095a97..29e833e178 100644 --- a/TUnit.Core.SourceGenerator/Generators/PropertyInjectionSourceGenerator.cs +++ b/TUnit.Core.SourceGenerator/Generators/PropertyInjectionSourceGenerator.cs @@ -38,6 +38,28 @@ public void Initialize(IncrementalGeneratorInitializationContext context) } GenerateIndividualPropertyInjectionSource(context, classData); }); + + // Also generate property sources for closed generic types used in data source attributes + // This ensures AOT compatibility for types like ErrFixture + var closedGenericTypesFromDataSources = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: (node, _) => IsClassWithDataSourceProperties(node), + transform: (ctx, _) => GetClosedGenericTypesFromDataSources(ctx)) + .Where(x => x != null) + .SelectMany((types, _) => types ?? []) + .Collect() + .SelectMany((classes, _) => classes.DistinctBy(c => c.ClassSymbol, SymbolEqualityComparer.Default)) + .Combine(enabledProvider); + + context.RegisterSourceOutput(closedGenericTypesFromDataSources, (context, data) => + { + var (classData, isEnabled) = data; + if (!isEnabled) + { + return; + } + GenerateIndividualPropertyInjectionSource(context, classData); + }); } private static bool IsClassWithDataSourceProperties(SyntaxNode node) @@ -45,6 +67,130 @@ private static bool IsClassWithDataSourceProperties(SyntaxNode node) return node is TypeDeclarationSyntax; } + /// + /// Extracts closed generic types from data source attributes (like ClassDataSource<ErrFixture<MyType>>) + /// that have injectable properties. This enables AOT-compatible property injection for generic types. + /// + private static IEnumerable? GetClosedGenericTypesFromDataSources(GeneratorSyntaxContext context) + { + var typeDecl = (TypeDeclarationSyntax)context.Node; + var semanticModel = context.SemanticModel; + + if (semanticModel.GetDeclaredSymbol(typeDecl) is not INamedTypeSymbol typeSymbol) + { + return null; + } + + var dataSourceInterface = semanticModel.Compilation.GetTypeByMetadataName("TUnit.Core.IDataSourceAttribute"); + if (dataSourceInterface == null) + { + return null; + } + + var closedGenericTypes = new List(); + var processedTypes = new HashSet(); + + // Check all properties for data source attributes that reference closed generic types + var allProperties = typeSymbol.GetMembers() + .OfType() + .Concat(typeSymbol.GetMembersIncludingBase().OfType()); + + foreach (var property in allProperties) + { + foreach (var attr in property.GetAttributes()) + { + if (attr.AttributeClass == null || + !attr.AttributeClass.AllInterfaces.Contains(dataSourceInterface, SymbolEqualityComparer.Default)) + { + continue; + } + + // Check if the attribute is a generic type like ClassDataSource + if (attr.AttributeClass.IsGenericType && attr.AttributeClass.TypeArguments.Length > 0) + { + foreach (var typeArg in attr.AttributeClass.TypeArguments) + { + if (typeArg is INamedTypeSymbol namedTypeArg && namedTypeArg.IsGenericType && + !namedTypeArg.IsUnboundGenericType && namedTypeArg.TypeParameters.Length == 0) + { + // This is a closed generic type (e.g., ErrFixture) + var fullName = namedTypeArg.ToDisplayString(); + if (!processedTypes.Add(fullName)) + { + continue; + } + + // Check if this type has properties with data source attributes + var classData = GetClassWithDataSourcePropertiesForType(namedTypeArg, semanticModel, dataSourceInterface); + if (classData != null) + { + closedGenericTypes.Add(classData); + } + } + } + } + } + } + + return closedGenericTypes.Count > 0 ? closedGenericTypes : null; + } + + /// + /// Creates a ClassWithDataSourceProperties for a specific type (used for closed generic types). + /// + private static ClassWithDataSourceProperties? GetClassWithDataSourcePropertiesForType( + INamedTypeSymbol typeSymbol, + SemanticModel semanticModel, + INamedTypeSymbol dataSourceInterface) + { + // Skip types that are not publicly accessible + if (!IsPubliclyAccessible(typeSymbol)) + { + return null; + } + + var propertiesWithDataSources = new List(); + var processedProperties = new HashSet(); + + var allProperties = typeSymbol.GetMembers() + .OfType() + .Where(CanSetProperty) + .ToList(); + + foreach (var property in allProperties) + { + if (!processedProperties.Add(property.Name)) + { + continue; + } + + foreach (var attr in property.GetAttributes()) + { + if (attr.AttributeClass != null && + attr.AttributeClass.AllInterfaces.Contains(dataSourceInterface, SymbolEqualityComparer.Default)) + { + propertiesWithDataSources.Add(new PropertyWithDataSourceAttribute + { + Property = property, + DataSourceAttribute = attr + }); + break; + } + } + } + + if (propertiesWithDataSources.Count == 0) + { + return null; + } + + return new ClassWithDataSourceProperties + { + ClassSymbol = typeSymbol, + Properties = propertiesWithDataSources.ToImmutableArray() + }; + } + private static ClassWithDataSourceProperties? GetClassWithDataSourceProperties(GeneratorSyntaxContext context) { var typeDecl = (TypeDeclarationSyntax)context.Node; diff --git a/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs b/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs index 5826707b2d..8e2e044856 100644 --- a/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs +++ b/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs @@ -937,15 +937,23 @@ private static void GenerateMethodDataSourceAttribute(CodeWriter writer, Attribu // Generate the attribute with factory // We need to manually construct this to properly add the Factory property - var attrClass = attr.AttributeClass!; - var attrTypeName = attrClass.GloballyQualified(); + + // Determine if the data source is static or instance-based + var isStatic = dataSourceMethod?.IsStatic ?? dataSourceProperty?.GetMethod?.IsStatic ?? true; + + // Use InstanceMethodDataSourceAttribute for instance-based data sources + // This implements IAccessesInstanceData which tells the engine to create an instance early + var attrTypeName = isStatic + ? "global::TUnit.Core.MethodDataSourceAttribute" + : "global::TUnit.Core.InstanceMethodDataSourceAttribute"; if (attr.ConstructorArguments is [ { Value: ITypeSymbol typeArg } _, _, .. ]) { - // MethodDataSource(Type, string) constructor + // MethodDataSource(Type, string) constructor - only available on MethodDataSourceAttribute + // For instance data sources, we still use the same constructor signature writer.AppendLine($"new {attrTypeName}(typeof({typeArg.GloballyQualified()}), \"{methodName}\")"); } else @@ -1048,7 +1056,7 @@ private static void GenerateMethodDataSourceFactory(CodeWriter writer, IMethodSy writer.AppendLine("else"); writer.AppendLine("{"); writer.Indent(); - writer.AppendLine($"instance = new {fullyQualifiedType}();"); + writer.AppendLine("throw new global::System.InvalidOperationException(\"Instance method data source requires TestClassInstance. This should have been provided by the engine.\");"); writer.Unindent(); writer.AppendLine("}"); writer.AppendLine($"var result = (({fullyQualifiedType})instance).{methodCall};"); @@ -1080,7 +1088,7 @@ private static void GenerateMethodDataSourceFactory(CodeWriter writer, IMethodSy writer.AppendLine("else"); writer.AppendLine("{"); writer.Indent(); - writer.AppendLine($"instance = new {fullyQualifiedType}();"); + writer.AppendLine("throw new global::System.InvalidOperationException(\"Instance method data source requires TestClassInstance. This should have been provided by the engine.\");"); writer.Unindent(); writer.AppendLine("}"); writer.AppendLine($"var result = (({fullyQualifiedType})instance).{methodCall};"); @@ -1124,7 +1132,7 @@ private static void GenerateMethodDataSourceFactory(CodeWriter writer, IMethodSy writer.AppendLine("else"); writer.AppendLine("{"); writer.Indent(); - writer.AppendLine($"instance = new {fullyQualifiedType}();"); + writer.AppendLine("throw new global::System.InvalidOperationException(\"Instance method data source requires TestClassInstance. This should have been provided by the engine.\");"); writer.Unindent(); writer.AppendLine("}"); writer.AppendLine($"var result = (({fullyQualifiedType})instance).{methodCall};"); @@ -1167,7 +1175,7 @@ private static void GenerateMethodDataSourceFactory(CodeWriter writer, IMethodSy writer.AppendLine("else"); writer.AppendLine("{"); writer.Indent(); - writer.AppendLine($"instance = new {fullyQualifiedType}();"); + writer.AppendLine("throw new global::System.InvalidOperationException(\"Instance method data source requires TestClassInstance. This should have been provided by the engine.\");"); writer.Unindent(); writer.AppendLine("}"); writer.AppendLine($"var result = (({fullyQualifiedType})instance).{methodCall};"); @@ -1217,7 +1225,7 @@ private static void GeneratePropertyDataSourceFactory(CodeWriter writer, IProper writer.AppendLine("else"); writer.AppendLine("{"); writer.Indent(); - writer.AppendLine($"instance = new {fullyQualifiedType}();"); + writer.AppendLine("throw new global::System.InvalidOperationException(\"Instance method data source requires TestClassInstance. This should have been provided by the engine.\");"); writer.Unindent(); writer.AppendLine("}"); writer.AppendLine($"var result = (({fullyQualifiedType})instance).{propertyAccess};"); @@ -1249,7 +1257,7 @@ private static void GeneratePropertyDataSourceFactory(CodeWriter writer, IProper writer.AppendLine("else"); writer.AppendLine("{"); writer.Indent(); - writer.AppendLine($"instance = new {fullyQualifiedType}();"); + writer.AppendLine("throw new global::System.InvalidOperationException(\"Instance method data source requires TestClassInstance. This should have been provided by the engine.\");"); writer.Unindent(); writer.AppendLine("}"); writer.AppendLine($"var result = (({fullyQualifiedType})instance).{propertyAccess};"); @@ -1293,7 +1301,7 @@ private static void GeneratePropertyDataSourceFactory(CodeWriter writer, IProper writer.AppendLine("else"); writer.AppendLine("{"); writer.Indent(); - writer.AppendLine($"instance = new {fullyQualifiedType}();"); + writer.AppendLine("throw new global::System.InvalidOperationException(\"Instance method data source requires TestClassInstance. This should have been provided by the engine.\");"); writer.Unindent(); writer.AppendLine("}"); writer.AppendLine($"var result = (({fullyQualifiedType})instance).{propertyAccess};"); @@ -1336,7 +1344,7 @@ private static void GeneratePropertyDataSourceFactory(CodeWriter writer, IProper writer.AppendLine("else"); writer.AppendLine("{"); writer.Indent(); - writer.AppendLine($"instance = new {fullyQualifiedType}();"); + writer.AppendLine("throw new global::System.InvalidOperationException(\"Instance method data source requires TestClassInstance. This should have been provided by the engine.\");"); writer.Unindent(); writer.AppendLine("}"); writer.AppendLine($"var result = (({fullyQualifiedType})instance).{propertyAccess};"); @@ -1876,16 +1884,15 @@ private static void GenerateTypedInvokers(CodeWriter writer, TestMethodMetadata InstanceFactoryGenerator.GenerateInstanceFactory(writer, testMethod.TypeSymbol, testMethod); // Generate InvokeTypedTest for non-generic tests - var isAsync = IsAsyncMethod(testMethod.MethodSymbol); - var returnsValueTask = ReturnsValueTask(testMethod.MethodSymbol); + var returnPattern = GetReturnPattern(testMethod.MethodSymbol); if (testMethod is { IsGenericType: false, IsGenericMethod: false }) { - GenerateConcreteTestInvoker(writer, testMethod, className, methodName, isAsync, returnsValueTask, hasCancellationToken, parametersFromArgs); + GenerateConcreteTestInvoker(writer, testMethod, className, methodName, returnPattern, hasCancellationToken, parametersFromArgs); } } - private static void GenerateConcreteTestInvoker(CodeWriter writer, TestMethodMetadata testMethod, string className, string methodName, bool isAsync, bool returnsValueTask, bool hasCancellationToken, IParameterSymbol[] parametersFromArgs) + private static void GenerateConcreteTestInvoker(CodeWriter writer, TestMethodMetadata testMethod, string className, string methodName, TestReturnPattern returnPattern, bool hasCancellationToken, IParameterSymbol[] parametersFromArgs) { // Generate InvokeTypedTest which is required by CreateExecutableTestFactory writer.AppendLine("InvokeTypedTest = static (instance, args, cancellationToken) =>"); @@ -1925,22 +1932,7 @@ private static void GenerateConcreteTestInvoker(CodeWriter writer, TestMethodMet var methodCallReconstructed = hasCancellationToken ? $"instance.{methodName}({tupleConstruction}, cancellationToken)" : $"instance.{methodName}({tupleConstruction})"; - if (isAsync) - { - if (returnsValueTask) - { - writer.AppendLine($"return {methodCallReconstructed};"); - } - else - { - writer.AppendLine($"return new global::System.Threading.Tasks.ValueTask({methodCallReconstructed});"); - } - } - else - { - writer.AppendLine($"{methodCallReconstructed};"); - writer.AppendLine("return default(global::System.Threading.Tasks.ValueTask);"); - } + GenerateReturnHandling(writer, methodCallReconstructed, returnPattern); writer.Unindent(); writer.AppendLine("}"); writer.AppendLine("else if (args.Length == 1 && global::TUnit.Core.Helpers.DataSourceHelpers.IsTuple(args[0]))"); @@ -1950,22 +1942,7 @@ private static void GenerateConcreteTestInvoker(CodeWriter writer, TestMethodMet var methodCallDirect = hasCancellationToken ? $"instance.{methodName}(TUnit.Core.Helpers.CastHelper.Cast<{singleTupleParam.GloballyQualified()}>(args[0]), cancellationToken)" : $"instance.{methodName}(TUnit.Core.Helpers.CastHelper.Cast<{singleTupleParam.GloballyQualified()}>(args[0]))"; - if (isAsync) - { - if (returnsValueTask) - { - writer.AppendLine($"return {methodCallDirect};"); - } - else - { - writer.AppendLine($"return new global::System.Threading.Tasks.ValueTask({methodCallDirect});"); - } - } - else - { - writer.AppendLine($"{methodCallDirect};"); - writer.AppendLine("return default(global::System.Threading.Tasks.ValueTask);"); - } + GenerateReturnHandling(writer, methodCallDirect, returnPattern); writer.Unindent(); writer.AppendLine("}"); writer.AppendLine("else"); @@ -1980,22 +1957,7 @@ private static void GenerateConcreteTestInvoker(CodeWriter writer, TestMethodMet var typedMethodCall = hasCancellationToken ? $"instance.{methodName}(cancellationToken)" : $"instance.{methodName}()"; - if (isAsync) - { - if (returnsValueTask) - { - writer.AppendLine($"return {typedMethodCall};"); - } - else - { - writer.AppendLine($"return new global::System.Threading.Tasks.ValueTask({typedMethodCall});"); - } - } - else - { - writer.AppendLine($"{typedMethodCall};"); - writer.AppendLine("return default(global::System.Threading.Tasks.ValueTask);"); - } + GenerateReturnHandling(writer, typedMethodCall, returnPattern); } else { @@ -2020,6 +1982,8 @@ private static void GenerateConcreteTestInvoker(CodeWriter writer, TestMethodMet var argCount = requiredParamCount + i; writer.AppendLine($"case {argCount}:"); writer.Indent(); + writer.AppendLine("{"); + writer.Indent(); // Build the arguments to pass, handling params arrays correctly var argsToPass = TupleArgumentHelper.GenerateArgumentAccessWithParams(parametersFromArgs, "args", argCount); @@ -2031,23 +1995,9 @@ private static void GenerateConcreteTestInvoker(CodeWriter writer, TestMethodMet } var typedMethodCall = $"instance.{methodName}({string.Join(", ", argsToPass)})"; - - if (isAsync) - { - if (returnsValueTask) - { - writer.AppendLine($"return {typedMethodCall};"); - } - else - { - writer.AppendLine($"return new global::System.Threading.Tasks.ValueTask({typedMethodCall});"); - } - } - else - { - writer.AppendLine($"{typedMethodCall};"); - writer.AppendLine("return default(global::System.Threading.Tasks.ValueTask);"); - } + GenerateReturnHandling(writer, typedMethodCall, returnPattern); + writer.Unindent(); + writer.AppendLine("}"); writer.Unindent(); } @@ -2115,6 +2065,66 @@ private static bool ReturnsValueTask(IMethodSymbol method) return returnTypeName.StartsWith("System.Threading.Tasks.ValueTask"); } + private enum TestReturnPattern + { + Void, // void methods + ValueTask, // ValueTask or ValueTask + Task, // Task or Task + Unknown // F# Async, custom awaitables, etc. + } + + private static TestReturnPattern GetReturnPattern(IMethodSymbol method) + { + if (method.ReturnType.SpecialType == SpecialType.System_Void) + { + return TestReturnPattern.Void; + } + + var returnTypeName = method.ReturnType.ToDisplayString(); + + if (returnTypeName.StartsWith("System.Threading.Tasks.ValueTask")) + { + return TestReturnPattern.ValueTask; + } + + if (returnTypeName.StartsWith("System.Threading.Tasks.Task") || + returnTypeName.StartsWith("Task<")) + { + return TestReturnPattern.Task; + } + + return TestReturnPattern.Unknown; + } + + private static void GenerateReturnHandling( + CodeWriter writer, + string methodCall, + TestReturnPattern returnPattern) + { + switch (returnPattern) + { + case TestReturnPattern.Void: + writer.AppendLine($"{methodCall};"); + writer.AppendLine("return default(global::System.Threading.Tasks.ValueTask);"); + break; + + case TestReturnPattern.ValueTask: + writer.AppendLine($"return {methodCall};"); + break; + + case TestReturnPattern.Task: + writer.AppendLine($"return new global::System.Threading.Tasks.ValueTask({methodCall});"); + break; + + case TestReturnPattern.Unknown: + // F# Async, custom awaitables + writer.AppendLine($"var methodResult = {methodCall};"); + writer.AppendLine("if (methodResult == null) return default(global::System.Threading.Tasks.ValueTask);"); + writer.AppendLine("return global::TUnit.Core.AsyncConvert.ConvertObject(methodResult);"); + break; + } + } + private static void GenerateDependencies(CodeWriter writer, Compilation compilation, IMethodSymbol methodSymbol) { var dependsOnAttributes = methodSymbol.GetAttributes() @@ -2341,11 +2351,83 @@ private static string GetDefaultValueString(IParameterSymbol parameter) } + private static bool IsMethodHiding(IMethodSymbol derivedMethod, IMethodSymbol baseMethod) + { + // Must have same name + if (derivedMethod.Name != baseMethod.Name) + { + return false; + } + + // Must NOT be an override (overrides are different from hiding) + if (derivedMethod.IsOverride) + { + return false; + } + + // Must have matching parameters + if (!ParametersMatch(derivedMethod.Parameters, baseMethod.Parameters)) + { + return false; + } + + // Derived method's containing type must be derived from base method's containing type + var derivedType = derivedMethod.ContainingType; + var baseType = baseMethod.ContainingType; + + // Can't hide yourself + if (SymbolEqualityComparer.Default.Equals(derivedType.OriginalDefinition, baseType.OriginalDefinition)) + { + return false; + } + + // Check if derived type inherits from base type + var current = derivedType.BaseType; + while (current is not null && current.SpecialType != SpecialType.System_Object) + { + if (SymbolEqualityComparer.Default.Equals(current.OriginalDefinition, baseType.OriginalDefinition)) + { + return true; + } + current = current.BaseType; + } + + return false; + } + private static List CollectInheritedTestMethods(INamedTypeSymbol derivedClass) { - return derivedClass.GetMembersIncludingBase().OfType() + var allTestMethods = derivedClass.GetMembersIncludingBase() + .OfType() .Where(m => m.GetAttributes().Any(attr => attr.IsTestAttribute())) .ToList(); + + // Find methods declared directly on the derived class + var derivedClassMethods = allTestMethods + .Where(m => SymbolEqualityComparer.Default.Equals(m.ContainingType.OriginalDefinition, derivedClass.OriginalDefinition)) + .ToList(); + + // Filter out base methods that are hidden by derived class methods or declared directly on derived class + var result = new List(); + foreach (var method in allTestMethods) + { + // Skip methods declared directly on derived class + // (they're handled by regular test registration) + if (SymbolEqualityComparer.Default.Equals(method.ContainingType.OriginalDefinition, derivedClass.OriginalDefinition)) + { + continue; + } + + // Check if this base method is hidden by any derived class method + var isHidden = derivedClassMethods.Any(derived => IsMethodHiding(derived, method)); + + if (!isHidden) + { + result.Add(method); + } + } + + return result; } private static IMethodSymbol? FindConcreteMethodImplementation(INamedTypeSymbol derivedClass, IMethodSymbol baseMethod) diff --git a/TUnit.Core/Attributes/TestData/ClassDataSources.cs b/TUnit.Core/Attributes/TestData/ClassDataSources.cs index a933e860b4..d6fc652455 100644 --- a/TUnit.Core/Attributes/TestData/ClassDataSources.cs +++ b/TUnit.Core/Attributes/TestData/ClassDataSources.cs @@ -43,11 +43,11 @@ private string GetKey(int index, SharedType[] sharedTypes, string[] keys) { return sharedType switch { - SharedType.None => Create(dataGeneratorMetadata), - SharedType.PerTestSession => (T) TestDataContainer.GetGlobalInstance(typeof(T), _ => Create(typeof(T), dataGeneratorMetadata))!, - SharedType.PerClass => (T) TestDataContainer.GetInstanceForClass(testClassType, typeof(T), _ => Create(typeof(T), dataGeneratorMetadata))!, - SharedType.Keyed => (T) TestDataContainer.GetInstanceForKey(key, typeof(T), _ => Create(typeof(T), dataGeneratorMetadata))!, - SharedType.PerAssembly => (T) TestDataContainer.GetInstanceForAssembly(testClassType.Assembly, typeof(T), _ => Create(typeof(T), dataGeneratorMetadata))!, + SharedType.None => Create(), + SharedType.PerTestSession => (T) TestDataContainer.GetGlobalInstance(typeof(T), _ => Create(typeof(T)))!, + SharedType.PerClass => (T) TestDataContainer.GetInstanceForClass(testClassType, typeof(T), _ => Create(typeof(T)))!, + SharedType.Keyed => (T) TestDataContainer.GetInstanceForKey(key, typeof(T), _ => Create(typeof(T)))!, + SharedType.PerAssembly => (T) TestDataContainer.GetInstanceForAssembly(testClassType.Assembly, typeof(T), _ => Create(typeof(T)))!, _ => throw new ArgumentOutOfRangeException() }; } @@ -56,41 +56,27 @@ private string GetKey(int index, SharedType[] sharedTypes, string[] keys) { return sharedType switch { - SharedType.None => Create(type, dataGeneratorMetadata), - SharedType.PerTestSession => TestDataContainer.GetGlobalInstance(type, _ => Create(type, dataGeneratorMetadata)), - SharedType.PerClass => TestDataContainer.GetInstanceForClass(testClassType, type, _ => Create(type, dataGeneratorMetadata)), - SharedType.Keyed => TestDataContainer.GetInstanceForKey(key!, type, _ => Create(type, dataGeneratorMetadata)), - SharedType.PerAssembly => TestDataContainer.GetInstanceForAssembly(testClassType.Assembly, type, _ => Create(type, dataGeneratorMetadata)), + SharedType.None => Create(type), + SharedType.PerTestSession => TestDataContainer.GetGlobalInstance(type, _ => Create(type)), + SharedType.PerClass => TestDataContainer.GetInstanceForClass(testClassType, type, _ => Create(type)), + SharedType.Keyed => TestDataContainer.GetInstanceForKey(key!, type, _ => Create(type)), + SharedType.PerAssembly => TestDataContainer.GetInstanceForAssembly(testClassType.Assembly, type, _ => Create(type)), _ => throw new ArgumentOutOfRangeException() }; } [return: NotNull] - private static T Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(DataGeneratorMetadata dataGeneratorMetadata) + private static T Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>() { - return ((T) Create(typeof(T), dataGeneratorMetadata))!; + return ((T) Create(typeof(T)))!; } - private static object Create([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type, DataGeneratorMetadata dataGeneratorMetadata) + private static object Create([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type type) { - return CreateWithNestedDependencies(type, dataGeneratorMetadata, recursionDepth: 0); - } - - private const int MaxRecursionDepth = 10; - - [UnconditionalSuppressMessage("Trimming", "IL2072:Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' requirements", - Justification = "PropertyType from PropertyInjectionMetadata has the required DynamicallyAccessedMembers annotations")] - private static object CreateWithNestedDependencies([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type, DataGeneratorMetadata dataGeneratorMetadata, int recursionDepth) - { - if (recursionDepth >= MaxRecursionDepth) - { - throw new InvalidOperationException($"Maximum recursion depth ({MaxRecursionDepth}) exceeded when creating nested ClassDataSource dependencies. This may indicate a circular dependency."); - } - - object instance; try { - instance = Activator.CreateInstance(type)!; + // Just create the instance - initialization happens in the Engine + return Activator.CreateInstance(type)!; } catch (TargetInvocationException targetInvocationException) { @@ -101,21 +87,5 @@ private static object CreateWithNestedDependencies([DynamicallyAccessedMembers(D throw; } - - // Populate nested ClassDataSource properties recursively - var propertySource = PropertySourceRegistry.GetSource(type); - if (propertySource?.ShouldInitialize == true) - { - var propertyMetadata = propertySource.GetPropertyMetadata(); - foreach (var metadata in propertyMetadata) - { - // Recursively create the property value using CreateWithNestedDependencies - // This will handle nested ClassDataSource properties - var propertyValue = CreateWithNestedDependencies(metadata.PropertyType, dataGeneratorMetadata, recursionDepth + 1); - metadata.SetProperty(instance, propertyValue); - } - } - - return instance; } } diff --git a/TUnit.Core/Attributes/TestData/CombinedDataSourcesAttribute.cs b/TUnit.Core/Attributes/TestData/CombinedDataSourcesAttribute.cs index e39d59f1bb..4d8ab15ef5 100644 --- a/TUnit.Core/Attributes/TestData/CombinedDataSourcesAttribute.cs +++ b/TUnit.Core/Attributes/TestData/CombinedDataSourcesAttribute.cs @@ -81,7 +81,7 @@ namespace TUnit.Core; /// /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] -public sealed class CombinedDataSourcesAttribute : AsyncUntypedDataSourceGeneratorAttribute, IAccessesInstanceData +public sealed class CombinedDataSourcesAttribute : AsyncUntypedDataSourceGeneratorAttribute { protected override async IAsyncEnumerable>> GenerateDataSourcesAsync(DataGeneratorMetadata dataGeneratorMetadata) { @@ -156,9 +156,12 @@ public sealed class CombinedDataSourcesAttribute : AsyncUntypedDataSourceGenerat if (dataSourceAttr is IAccessesInstanceData && dataGeneratorMetadata.TestClassInstance == null) { var className = dataGeneratorMetadata.TestInformation?.Class.Type.Name ?? "Unknown"; + var attrName = dataSourceAttr.GetType().Name; throw new InvalidOperationException( - $"Cannot use instance-based data source attribute on parameter '{parameterMetadata.Name}' when no instance is available. " + - $"Consider using static data sources or ensure the test class is properly instantiated."); + $"Cannot use instance-based data source '{attrName}' on parameter '{parameterMetadata.Name}' in class '{className}'. " + + $"When [CombinedDataSources] is applied at the class level (constructor parameters), all data sources must be static " + + $"because no instance exists yet. Use static [MethodDataSource] or [Arguments] instead, " + + $"or move [CombinedDataSources] to the method level if you need instance-based data sources."); } // Create metadata for this single parameter diff --git a/TUnit.Core/Attributes/TestData/InstanceMethodDataSourceSourceAttribute.cs b/TUnit.Core/Attributes/TestData/InstanceMethodDataSourceSourceAttribute.cs index 28664cdc9f..9413df05a1 100644 --- a/TUnit.Core/Attributes/TestData/InstanceMethodDataSourceSourceAttribute.cs +++ b/TUnit.Core/Attributes/TestData/InstanceMethodDataSourceSourceAttribute.cs @@ -1,4 +1,25 @@ -namespace TUnit.Core; +using System.Diagnostics.CodeAnalysis; +namespace TUnit.Core; + +/// +/// A method data source attribute that requires an instance of the test class to be created first. +/// This implements IAccessesInstanceData which tells the engine to create a properly-initialized +/// instance before evaluating the data source. +/// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] -public class InstanceMethodDataSourceAttribute(string methodNameProvidingDataSource) : MethodDataSourceAttribute(methodNameProvidingDataSource), IAccessesInstanceData; +public class InstanceMethodDataSourceAttribute : MethodDataSourceAttribute, IAccessesInstanceData +{ + public InstanceMethodDataSourceAttribute(string methodNameProvidingDataSource) + : base(methodNameProvidingDataSource) + { + } + + public InstanceMethodDataSourceAttribute( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] + Type classProvidingDataSource, + string methodNameProvidingDataSource) + : base(classProvidingDataSource, methodNameProvidingDataSource) + { + } +} diff --git a/TUnit.Core/Attributes/TestData/MethodDataSourceAttribute.cs b/TUnit.Core/Attributes/TestData/MethodDataSourceAttribute.cs index efa53d7741..c6bd260fe0 100644 --- a/TUnit.Core/Attributes/TestData/MethodDataSourceAttribute.cs +++ b/TUnit.Core/Attributes/TestData/MethodDataSourceAttribute.cs @@ -82,7 +82,10 @@ public MethodDataSourceAttribute( // If we have a test class instance and no explicit class was provided, // use the instance's actual type (which will be the constructed generic type) - if (ClassProvidingDataSource == null && dataGeneratorMetadata.TestClassInstance != null) + // Skip PlaceholderInstance as it's used during discovery when the actual instance isn't created yet + if (ClassProvidingDataSource == null + && dataGeneratorMetadata.TestClassInstance != null + && dataGeneratorMetadata.TestClassInstance is not PlaceholderInstance) { targetType = dataGeneratorMetadata.TestClassInstance.GetType(); } diff --git a/TUnit.Core/Context.cs b/TUnit.Core/Context.cs index caf28bec33..9c1346e0a8 100644 --- a/TUnit.Core/Context.cs +++ b/TUnit.Core/Context.cs @@ -14,6 +14,7 @@ protected Context? Parent public static Context Current => TestContext.Current as Context + ?? TestBuildContext.Current as Context ?? ClassHookContext.Current as Context ?? AssemblyHookContext.Current as Context ?? TestSessionContext.Current as Context @@ -67,13 +68,13 @@ public void AddAsyncLocalValues() #endif } - public string GetStandardOutput() + public virtual string GetStandardOutput() { if (_outputBuilder.Length == 0) { return string.Empty; } - + _outputLock.EnterReadLock(); try @@ -86,7 +87,7 @@ public string GetStandardOutput() } } - public string GetErrorOutput() + public virtual string GetErrorOutput() { if (_errorOutputBuilder.Length == 0) { diff --git a/TUnit.Core/Data/ScopedDictionary.cs b/TUnit.Core/Data/ScopedDictionary.cs index 7a751429ff..e5766792b0 100644 --- a/TUnit.Core/Data/ScopedDictionary.cs +++ b/TUnit.Core/Data/ScopedDictionary.cs @@ -1,6 +1,4 @@ -using TUnit.Core.Tracking; - -namespace TUnit.Core.Data; +namespace TUnit.Core.Data; public class ScopedDictionary where TScope : notnull @@ -11,14 +9,6 @@ public class ScopedDictionary { var innerDictionary = _scopedContainers.GetOrAdd(scope, static _ => new ThreadSafeDictionary()); - var obj = innerDictionary.GetOrAdd(type, factory); - - ObjectTracker.OnDisposed(obj, () => - { - innerDictionary.Remove(type); - }); - - return obj; + return innerDictionary.GetOrAdd(type, factory); } - } diff --git a/TUnit.Core/Data/ThreadSafeDictionary.cs b/TUnit.Core/Data/ThreadSafeDictionary.cs index 6744f5f638..c9e5665c48 100644 --- a/TUnit.Core/Data/ThreadSafeDictionary.cs +++ b/TUnit.Core/Data/ThreadSafeDictionary.cs @@ -69,12 +69,30 @@ public class ThreadSafeDictionary /// This method is thread-safe. If multiple threads call this method simultaneously with the same key, /// the factory function will be executed only once, and all threads will receive the same instance. + /// This implementation uses a two-phase approach: TryGetValue for the fast path (when key exists), + /// and GetOrAdd with a pre-created Lazy for the slow path (new key). This prevents the factory + /// from being invoked multiple times during concurrent access. /// public TValue GetOrAdd(TKey key, Func func) { - var lazy = _innerDictionary.GetOrAdd(key, - k => new Lazy(() => func(k), LazyThreadSafetyMode.ExecutionAndPublication)); - return lazy.Value; + // Fast path: Check if key already exists (lock-free read) + if (_innerDictionary.TryGetValue(key, out var existingLazy)) + { + return existingLazy.Value; + } + + // Slow path: Key not found, need to create + // Create Lazy instance OUTSIDE of GetOrAdd to prevent factory from running during race + var newLazy = new Lazy(() => func(key), LazyThreadSafetyMode.ExecutionAndPublication); + + // Use GetOrAdd with VALUE (not factory) - atomic operation that either: + // 1. Adds our newLazy if key still doesn't exist + // 2. Returns existing Lazy if another thread just added one + var winningLazy = _innerDictionary.GetOrAdd(key, newLazy); + + // CRITICAL: Always return value from the Lazy that's actually in the dictionary + // This ensures only ONE factory execution even if multiple Lazy instances were created + return winningLazy.Value; } /// diff --git a/TUnit.Core/DataGeneratorMetadataCreator.cs b/TUnit.Core/DataGeneratorMetadataCreator.cs index cc0b713f70..8abfb23152 100644 --- a/TUnit.Core/DataGeneratorMetadataCreator.cs +++ b/TUnit.Core/DataGeneratorMetadataCreator.cs @@ -170,6 +170,7 @@ public static DataGeneratorMetadata CreateForPropertyInjection( PropertyMetadata propertyMetadata, MethodMetadata? methodMetadata, IDataSourceAttribute dataSource, + string testSessionId, TestContext? testContext = null, object? testClassInstance = null, TestContextEvents? events = null, @@ -193,7 +194,7 @@ public static DataGeneratorMetadata CreateForPropertyInjection( MembersToGenerate = [propertyMetadata], TestInformation = methodMetadata, Type = DataGeneratorType.Property, - TestSessionId = TestSessionContext.Current?.Id ?? "property-injection", + TestSessionId = testSessionId, TestClassInstance = testClassInstance ?? testContext?.Metadata.TestDetails.ClassInstance, ClassInstanceArguments = testContext?.Metadata.TestDetails.TestClassArguments ?? [] }; @@ -215,6 +216,7 @@ public static DataGeneratorMetadata CreateForPropertyInjection( Type containingType, MethodMetadata? methodMetadata, IDataSourceAttribute dataSource, + string testSessionId, TestContext? testContext = null, object? testClassInstance = null, TestContextEvents? events = null, @@ -235,6 +237,7 @@ public static DataGeneratorMetadata CreateForPropertyInjection( propertyMetadata, methodMetadata, dataSource, + testSessionId, testContext, testClassInstance, events, diff --git a/TUnit.Core/Discovery/ObjectGraph.cs b/TUnit.Core/Discovery/ObjectGraph.cs new file mode 100644 index 0000000000..85a1b6f960 --- /dev/null +++ b/TUnit.Core/Discovery/ObjectGraph.cs @@ -0,0 +1,124 @@ +using System.Collections.Concurrent; +using System.Collections.ObjectModel; +using TUnit.Core.Interfaces; + +namespace TUnit.Core.Discovery; + +/// +/// Represents a discovered object graph organized by depth level. +/// +/// +/// Internal collections are stored privately and exposed as read-only views +/// to prevent callers from corrupting internal state. +/// Uses Lazy<T> for thread-safe lazy initialization of read-only views. +/// +internal sealed class ObjectGraph : IObjectGraph +{ + private readonly ConcurrentDictionary> _objectsByDepth; + private readonly HashSet _allObjects; + + // Thread-safe lazy initialization of read-only views + private readonly Lazy>> _lazyReadOnlyObjectsByDepth; + private readonly Lazy> _lazyReadOnlyAllObjects; + + // Cached sorted depths (computed once in constructor) + private readonly int[] _sortedDepthsDescending; + + /// + /// Creates a new object graph from the discovered objects. + /// + /// Objects organized by depth level. + /// All unique objects in the graph. + public ObjectGraph(ConcurrentDictionary> objectsByDepth, HashSet allObjects) + { + _objectsByDepth = objectsByDepth; + _allObjects = allObjects; + + // Compute MaxDepth and sorted depths without LINQ to reduce allocations + var keyCount = objectsByDepth.Count; + if (keyCount == 0) + { + MaxDepth = -1; + _sortedDepthsDescending = []; + } + else + { + var keys = new int[keyCount]; + objectsByDepth.Keys.CopyTo(keys, 0); + + // Find max manually + var maxDepth = int.MinValue; + foreach (var key in keys) + { + if (key > maxDepth) + { + maxDepth = key; + } + } + MaxDepth = maxDepth; + + // Sort in descending order using Array.Sort with reverse comparison + Array.Sort(keys, (a, b) => b.CompareTo(a)); + _sortedDepthsDescending = keys; + } + + // Use Lazy with ExecutionAndPublication for thread-safe single initialization + _lazyReadOnlyObjectsByDepth = new Lazy>>( + CreateReadOnlyObjectsByDepth, + LazyThreadSafetyMode.ExecutionAndPublication); + + _lazyReadOnlyAllObjects = new Lazy>( + () => _allObjects.ToArray(), + LazyThreadSafetyMode.ExecutionAndPublication); + } + + /// + public IReadOnlyDictionary> ObjectsByDepth => _lazyReadOnlyObjectsByDepth.Value; + + /// + public IReadOnlyCollection AllObjects => _lazyReadOnlyAllObjects.Value; + + /// + public int MaxDepth { get; } + + /// + public IEnumerable GetObjectsAtDepth(int depth) + { + if (!_objectsByDepth.TryGetValue(depth, out var objects)) + { + return []; + } + + // Lock and copy to prevent concurrent modification issues + lock (objects) + { + return objects.ToArray(); + } + } + + /// + public IEnumerable GetDepthsDescending() + { + // Return cached sorted depths (computed once in constructor) + return _sortedDepthsDescending; + } + + /// + /// Creates a thread-safe read-only snapshot of objects by depth. + /// + private IReadOnlyDictionary> CreateReadOnlyObjectsByDepth() + { + var dict = new Dictionary>(_objectsByDepth.Count); + + foreach (var kvp in _objectsByDepth) + { + // Lock each HashSet while copying to ensure consistency + lock (kvp.Value) + { + dict[kvp.Key] = kvp.Value.ToArray(); + } + } + + return new ReadOnlyDictionary>(dict); + } +} diff --git a/TUnit.Core/Discovery/ObjectGraphDiscoverer.cs b/TUnit.Core/Discovery/ObjectGraphDiscoverer.cs new file mode 100644 index 0000000000..a4591d65b4 --- /dev/null +++ b/TUnit.Core/Discovery/ObjectGraphDiscoverer.cs @@ -0,0 +1,584 @@ +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using TUnit.Core.Helpers; +using TUnit.Core.Interfaces; +using TUnit.Core.Interfaces.SourceGenerator; +using TUnit.Core.PropertyInjection; + +namespace TUnit.Core.Discovery; + +/// +/// Represents an error that occurred during object graph discovery. +/// +/// The name of the type being inspected. +/// The name of the property that failed to access. +/// The error message. +/// The exception that occurred. +internal readonly record struct DiscoveryError(string TypeName, string PropertyName, string ErrorMessage, Exception Exception); + +/// +/// Centralized service for discovering and organizing object graphs. +/// Consolidates duplicate graph traversal logic from ObjectGraphDiscoveryService and TrackableObjectGraphProvider. +/// Follows Single Responsibility Principle - only discovers objects, doesn't modify them. +/// +/// +/// +/// This class is thread-safe and uses cached reflection for performance. +/// Objects are organized by their nesting depth in the hierarchy: +/// +/// +/// Depth 0: Root objects (class args, method args, property values) +/// Depth 1+: Nested objects found in properties of objects at previous depth +/// +/// +/// Discovery errors (e.g., property access failures) are collected in +/// rather than thrown, allowing discovery to continue despite individual property failures. +/// +/// +internal sealed class ObjectGraphDiscoverer : IObjectGraphTracker +{ + /// + /// Maximum recursion depth for object graph discovery. + /// Prevents stack overflow on deep or circular object graphs. + /// + private const int MaxRecursionDepth = 50; + + // Reference equality comparer for object tracking (ignores Equals overrides) + private static readonly Helpers.ReferenceEqualityComparer ReferenceComparer = Helpers.ReferenceEqualityComparer.Instance; + + // Types to skip during discovery (primitives, strings, system types) + private static readonly HashSet SkipTypes = + [ + typeof(string), + typeof(decimal), + typeof(DateTime), + typeof(DateTimeOffset), + typeof(TimeSpan), + typeof(Guid) + ]; + + // Thread-safe collection of discovery errors for diagnostics + private static readonly ConcurrentBag DiscoveryErrors = []; + + /// + /// Gets all discovery errors that occurred during object graph traversal. + /// Useful for debugging and diagnostics when property access fails. + /// + /// A read-only list of discovery errors. + public static IReadOnlyList GetDiscoveryErrors() + { + return DiscoveryErrors.ToArray(); + } + + /// + /// Clears all recorded discovery errors. Call at end of test session. + /// + public static void ClearDiscoveryErrors() + { + DiscoveryErrors.Clear(); + } + + /// + /// Delegate for adding discovered objects to collections. + /// Returns true if the object was newly added (not a duplicate). + /// + private delegate bool TryAddObjectFunc(object obj, int depth); + + /// + /// Delegate for recursive discovery after an object is added. + /// + private delegate void RecurseFunc(object obj, int depth); + + /// + /// Delegate for processing a root object after it's been added. + /// + private delegate void RootObjectCallback(object obj); + + /// + public IObjectGraph DiscoverObjectGraph(TestContext testContext, CancellationToken cancellationToken = default) + { + var objectsByDepth = new ConcurrentDictionary>(); + var allObjects = new HashSet(ReferenceComparer); + var allObjectsLock = new object(); // Thread-safety for allObjects HashSet + var visitedObjects = new ConcurrentDictionary(ReferenceComparer); + + // Standard mode add callback (thread-safe) + bool TryAddStandard(object obj, int depth) + { + if (!visitedObjects.TryAdd(obj, 0)) + { + return false; + } + + AddToDepth(objectsByDepth, depth, obj); + lock (allObjectsLock) + { + allObjects.Add(obj); + } + + return true; + } + + // Collect root-level objects and discover nested objects + CollectRootObjects( + testContext.Metadata.TestDetails, + TryAddStandard, + obj => DiscoverNestedObjects(obj, objectsByDepth, visitedObjects, allObjects, allObjectsLock, currentDepth: 1, cancellationToken), + cancellationToken); + + return new ObjectGraph(objectsByDepth, allObjects); + } + + /// + public IObjectGraph DiscoverNestedObjectGraph(object rootObject, CancellationToken cancellationToken = default) + { + var objectsByDepth = new ConcurrentDictionary>(); + var allObjects = new HashSet(ReferenceComparer); + var allObjectsLock = new object(); // Thread-safety for allObjects HashSet + var visitedObjects = new ConcurrentDictionary(ReferenceComparer); + + if (visitedObjects.TryAdd(rootObject, 0)) + { + AddToDepth(objectsByDepth, 0, rootObject); + lock (allObjectsLock) + { + allObjects.Add(rootObject); + } + + DiscoverNestedObjects(rootObject, objectsByDepth, visitedObjects, allObjects, allObjectsLock, currentDepth: 1, cancellationToken); + } + + return new ObjectGraph(objectsByDepth, allObjects); + } + + /// + /// Discovers objects and adds them to the existing tracked objects dictionary. + /// Used by TrackableObjectGraphProvider to populate TestContext.TrackedObjects. + /// + /// The test context to discover objects from. + /// Cancellation token for the operation. + /// The tracked objects dictionary (same as testContext.TrackedObjects). + public ConcurrentDictionary> DiscoverAndTrackObjects(TestContext testContext, CancellationToken cancellationToken = default) + { + var visitedObjects = testContext.TrackedObjects; + + // Collect root-level objects and discover nested objects for tracking + CollectRootObjects( + testContext.Metadata.TestDetails, + (obj, depth) => TryAddToHashSet(visitedObjects, depth, obj), + obj => DiscoverNestedObjectsForTracking(obj, visitedObjects, 1, cancellationToken), + cancellationToken); + + return visitedObjects; + } + + /// + /// Recursively discovers nested objects that have injectable properties OR implement IAsyncInitializer. + /// Uses consolidated TraverseInjectableProperties and TraverseInitializerProperties methods. + /// + private void DiscoverNestedObjects( + object obj, + ConcurrentDictionary> objectsByDepth, + ConcurrentDictionary visitedObjects, + HashSet allObjects, + object allObjectsLock, + int currentDepth, + CancellationToken cancellationToken) + { + if (!CheckRecursionDepth(obj, currentDepth)) + { + return; + } + + cancellationToken.ThrowIfCancellationRequested(); + + // Standard mode add callback: visitedObjects + objectsByDepth + allObjects (thread-safe) + bool TryAddStandard(object value, int depth) + { + if (!visitedObjects.TryAdd(value, 0)) + { + return false; + } + + AddToDepth(objectsByDepth, depth, value); + lock (allObjectsLock) + { + allObjects.Add(value); + } + + return true; + } + + // Recursive callback + void Recurse(object value, int depth) + { + DiscoverNestedObjects(value, objectsByDepth, visitedObjects, allObjects, allObjectsLock, depth, cancellationToken); + } + + // Traverse injectable properties (useSourceRegistrarCheck = false) + TraverseInjectableProperties(obj, TryAddStandard, Recurse, currentDepth, cancellationToken, useSourceRegistrarCheck: false); + + // Also discover nested IAsyncInitializer objects from ALL properties + TraverseInitializerProperties(obj, TryAddStandard, Recurse, currentDepth, cancellationToken); + } + + /// + /// Discovers nested objects for tracking (uses HashSet pattern for compatibility with TestContext.TrackedObjects). + /// Uses consolidated TraverseInjectableProperties and TraverseInitializerProperties methods. + /// + private void DiscoverNestedObjectsForTracking( + object obj, + ConcurrentDictionary> visitedObjects, + int currentDepth, + CancellationToken cancellationToken) + { + if (!CheckRecursionDepth(obj, currentDepth)) + { + return; + } + + cancellationToken.ThrowIfCancellationRequested(); + + // Tracking mode add callback: TryAddToHashSet only + bool TryAddTracking(object value, int depth) + { + return TryAddToHashSet(visitedObjects, depth, value); + } + + // Recursive callback + void Recurse(object value, int depth) + { + DiscoverNestedObjectsForTracking(value, visitedObjects, depth, cancellationToken); + } + + // Traverse injectable properties (useSourceRegistrarCheck = true for tracking mode) + TraverseInjectableProperties(obj, TryAddTracking, Recurse, currentDepth, cancellationToken, useSourceRegistrarCheck: true); + + // Also discover nested IAsyncInitializer objects from ALL properties + TraverseInitializerProperties(obj, TryAddTracking, Recurse, currentDepth, cancellationToken); + } + + /// + /// Clears all caches. Called at end of test session to release memory. + /// + public static void ClearCache() + { + PropertyCacheManager.ClearCache(); + ClearDiscoveryErrors(); + } + + /// + /// Checks if a type should be skipped during discovery. + /// + private static bool ShouldSkipType(Type type) + { + return type.IsPrimitive || + SkipTypes.Contains(type) || + type.Namespace?.StartsWith("System") == true; + } + + /// + /// Adds an object to the specified depth level. + /// Thread-safe: uses lock to protect HashSet modifications. + /// + private static void AddToDepth(ConcurrentDictionary> objectsByDepth, int depth, object obj) + { + var hashSet = objectsByDepth.GetOrAdd(depth, _ => new HashSet(ReferenceComparer)); + lock (hashSet) + { + hashSet.Add(obj); + } + } + + /// + /// Thread-safe add to HashSet at specified depth. Returns true if added (not duplicate). + /// + private static bool TryAddToHashSet(ConcurrentDictionary> dict, int depth, object obj) + { + var hashSet = dict.GetOrAdd(depth, _ => new HashSet(ReferenceComparer)); + lock (hashSet) + { + return hashSet.Add(obj); + } + } + + #region Consolidated Traversal Methods (DRY) + + /// + /// Checks recursion depth guard. Returns false if depth exceeded (caller should return early). + /// + private static bool CheckRecursionDepth(object obj, int currentDepth) + { + if (currentDepth > MaxRecursionDepth) + { +#if DEBUG + Debug.WriteLine($"[ObjectGraphDiscoverer] Max recursion depth ({MaxRecursionDepth}) reached for type '{obj.GetType().Name}'"); +#endif + return false; + } + + return true; + } + + /// + /// Unified traversal for injectable properties (from PropertyInjectionCache). + /// Eliminates duplicate code between DiscoverNestedObjects and DiscoverNestedObjectsForTracking. + /// + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Property discovery handles both AOT and reflection modes")] + private static void TraverseInjectableProperties( + object obj, + TryAddObjectFunc tryAdd, + RecurseFunc recurse, + int currentDepth, + CancellationToken cancellationToken, + bool useSourceRegistrarCheck) + { + var plan = PropertyInjectionCache.GetOrCreatePlan(obj.GetType()); + + if (!plan.HasProperties && !useSourceRegistrarCheck) + { + return; + } + + // The two modes differ in how they choose source-gen vs reflection: + // - Standard mode: Uses plan.SourceGeneratedProperties.Length > 0 + // - Tracking mode: Uses SourceRegistrar.IsEnabled + bool useSourceGen = useSourceRegistrarCheck + ? SourceRegistrar.IsEnabled + : plan.SourceGeneratedProperties.Length > 0; + + if (useSourceGen) + { + TraverseSourceGeneratedProperties(obj, plan.SourceGeneratedProperties, tryAdd, recurse, currentDepth, cancellationToken); + } + else + { + var reflectionProps = useSourceRegistrarCheck + ? plan.ReflectionProperties + : (plan.ReflectionProperties.Length > 0 ? plan.ReflectionProperties : []); + + TraverseReflectionProperties(obj, reflectionProps, tryAdd, recurse, currentDepth, cancellationToken); + } + } + + /// + /// Traverses source-generated properties and discovers nested objects. + /// Extracted for reduced complexity in TraverseInjectableProperties. + /// + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Property discovery handles both AOT and reflection modes")] + private static void TraverseSourceGeneratedProperties( + object obj, + PropertyInjectionMetadata[] sourceGeneratedProperties, + TryAddObjectFunc tryAdd, + RecurseFunc recurse, + int currentDepth, + CancellationToken cancellationToken) + { + foreach (var metadata in sourceGeneratedProperties) + { + cancellationToken.ThrowIfCancellationRequested(); + var property = metadata.ContainingType.GetProperty(metadata.PropertyName); + if (property == null || !property.CanRead) + { + continue; + } + + var value = property.GetValue(obj); + if (value != null && tryAdd(value, currentDepth)) + { + recurse(value, currentDepth + 1); + } + } + } + + /// + /// Traverses reflection-based properties and discovers nested objects. + /// Extracted for reduced complexity in TraverseInjectableProperties. + /// + private static void TraverseReflectionProperties( + object obj, + (PropertyInfo Property, IDataSourceAttribute DataSource)[] reflectionProperties, + TryAddObjectFunc tryAdd, + RecurseFunc recurse, + int currentDepth, + CancellationToken cancellationToken) + { + foreach (var prop in reflectionProperties) + { + cancellationToken.ThrowIfCancellationRequested(); + var value = prop.Property.GetValue(obj); + if (value != null && tryAdd(value, currentDepth)) + { + recurse(value, currentDepth + 1); + } + } + } + + /// + /// Unified traversal for IAsyncInitializer objects (from all properties). + /// Eliminates duplicate code between DiscoverNestedInitializerObjects and DiscoverNestedInitializerObjectsForTracking. + /// + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Reflection fallback for nested initializers. In AOT, source-gen handles primary discovery.")] + [UnconditionalSuppressMessage("Trimming", "IL2070", Justification = "Reflection fallback for nested initializers. In AOT, source-gen handles primary discovery.")] + [UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Reflection fallback for nested initializers. In AOT, source-gen handles primary discovery.")] + private static void TraverseInitializerProperties( + object obj, + TryAddObjectFunc tryAdd, + RecurseFunc recurse, + int currentDepth, + CancellationToken cancellationToken) + { + var type = obj.GetType(); + + if (ShouldSkipType(type)) + { + return; + } + + var properties = PropertyCacheManager.GetCachedProperties(type); + + foreach (var property in properties) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + var value = property.GetValue(obj); + if (value == null) + { + continue; + } + + // Only discover IAsyncInitializer objects + if (value is IAsyncInitializer && tryAdd(value, currentDepth)) + { + recurse(value, currentDepth + 1); + } + } + catch (OperationCanceledException) + { + throw; // Propagate cancellation + } + catch (Exception ex) + { + // Record error for diagnostics (available via GetDiscoveryErrors()) + DiscoveryErrors.Add(new DiscoveryError(type.Name, property.Name, ex.Message, ex)); +#if DEBUG + Debug.WriteLine($"[ObjectGraphDiscoverer] Failed to access property '{property.Name}' on type '{type.Name}': {ex.Message}"); +#endif + // Continue discovery despite property access failures + } + } + } + + /// + /// Collects root-level objects (class args, method args, properties) from test details. + /// Eliminates duplicate loops in DiscoverObjectGraph and DiscoverAndTrackObjects. + /// + /// + /// For injected properties, only DIRECT test class properties (including inherited) are added at depth 0. + /// Nested properties (properties of injected objects) are discovered through normal + /// graph traversal at appropriate depths (1+), ensuring correct initialization order + /// for nested IAsyncInitializer dependencies. See GitHub issue #4032. + /// + private static void CollectRootObjects( + TestDetails testDetails, + TryAddObjectFunc tryAdd, + RootObjectCallback onRootObjectAdded, + CancellationToken cancellationToken) + { + // Process class arguments + ProcessRootCollection(testDetails.TestClassArguments, tryAdd, onRootObjectAdded, cancellationToken); + + // Process method arguments + ProcessRootCollection(testDetails.TestMethodArguments, tryAdd, onRootObjectAdded, cancellationToken); + + // Build set of types in the test class hierarchy (for identifying direct properties) + var hierarchyTypes = GetTypeHierarchy(testDetails.ClassType); + + // Process ONLY direct test class injected properties at depth 0. + // Nested properties will be discovered through normal graph traversal at depth 1+. + // This ensures proper initialization order for nested IAsyncInitializer dependencies. + foreach (var kvp in testDetails.TestClassInjectedPropertyArguments) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (kvp.Value == null) + { + continue; + } + + // Check if this property belongs to the test class hierarchy (not nested object properties) + // Cache key format: "{DeclaringType.FullName}.{PropertyName}" + if (IsDirectProperty(kvp.Key, hierarchyTypes)) + { + if (tryAdd(kvp.Value, 0)) + { + onRootObjectAdded(kvp.Value); + } + } + } + } + + /// + /// Gets all types in the inheritance hierarchy from the given type up to (but not including) object. + /// + private static HashSet GetTypeHierarchy(Type type) + { + var result = new HashSet(); + var currentType = type; + + while (currentType != null && currentType != typeof(object)) + { + if (currentType.FullName != null) + { + result.Add(currentType.FullName); + } + + currentType = currentType.BaseType; + } + + return result; + } + + /// + /// Determines if a cache key represents a direct property (belonging to test class hierarchy) + /// vs a nested property (belonging to an injected object). + /// Cache key format: "{DeclaringType.FullName}.{PropertyName}" + /// + private static bool IsDirectProperty(string cacheKey, HashSet hierarchyTypes) + { + // Find the last dot to separate type from property name + var lastDotIndex = cacheKey.LastIndexOf('.'); + if (lastDotIndex <= 0) + { + return true; // Malformed key, treat as direct + } + + var declaringTypeName = cacheKey.Substring(0, lastDotIndex); + return hierarchyTypes.Contains(declaringTypeName); + } + + /// + /// Processes a collection of root objects, adding them to the graph and invoking callback. + /// Extracted to eliminate duplicate iteration patterns in CollectRootObjects. + /// + private static void ProcessRootCollection( + IEnumerable collection, + TryAddObjectFunc tryAdd, + RootObjectCallback onRootObjectAdded, + CancellationToken cancellationToken) + { + foreach (var item in collection) + { + cancellationToken.ThrowIfCancellationRequested(); + if (item != null && tryAdd(item, 0)) + { + onRootObjectAdded(item); + } + } + } + + #endregion +} diff --git a/TUnit.Core/Discovery/PropertyCacheManager.cs b/TUnit.Core/Discovery/PropertyCacheManager.cs new file mode 100644 index 0000000000..12319608cc --- /dev/null +++ b/TUnit.Core/Discovery/PropertyCacheManager.cs @@ -0,0 +1,116 @@ +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +namespace TUnit.Core.Discovery; + +/// +/// Manages cached property reflection results for object graph discovery. +/// Extracted from ObjectGraphDiscoverer to follow Single Responsibility Principle. +/// +/// +/// +/// This class caches arrays per type to avoid repeated reflection calls. +/// Includes automatic cache cleanup when size exceeds to prevent memory leaks. +/// +/// +/// Thread-safe: Uses and for coordination. +/// +/// +internal static class PropertyCacheManager +{ + /// + /// Maximum size for the property cache before cleanup is triggered. + /// Prevents unbounded memory growth in long-running test sessions. + /// + private const int MaxCacheSize = 10000; + + // Cache for GetProperties() results per type - eliminates repeated reflection calls + private static readonly ConcurrentDictionary PropertyCache = new(); + + // Flag to coordinate cache cleanup (prevents multiple threads cleaning simultaneously) + private static int _cleanupInProgress; + + /// + /// Gets cached properties for a type, filtering to only readable non-indexed properties. + /// Includes periodic cache cleanup to prevent unbounded memory growth. + /// + /// The type to get properties for. + /// An array of readable, non-indexed properties for the type. + [UnconditionalSuppressMessage("Trimming", "IL2070", Justification = "Reflection fallback for nested initializers. In AOT, source-gen handles primary discovery.")] + public static PropertyInfo[] GetCachedProperties(Type type) + { + // Periodic cleanup if cache grows too large to prevent memory leaks + // Use Interlocked to ensure only one thread performs cleanup at a time + if (PropertyCache.Count > MaxCacheSize && + Interlocked.CompareExchange(ref _cleanupInProgress, 1, 0) == 0) + { + try + { + // Double-check after acquiring cleanup flag + if (PropertyCache.Count > MaxCacheSize) + { + // Use ToArray() to get a true snapshot for thread-safe enumeration + // This prevents issues with concurrent modifications during iteration + var allKeys = PropertyCache.Keys.ToArray(); + var removeCount = Math.Min(allKeys.Length / 2, MaxCacheSize / 2); + + for (var i = 0; i < removeCount; i++) + { + PropertyCache.TryRemove(allKeys[i], out _); + } +#if DEBUG + Debug.WriteLine($"[PropertyCacheManager] PropertyCache exceeded {MaxCacheSize} entries, cleared {removeCount} entries"); +#endif + } + } + finally + { + Interlocked.Exchange(ref _cleanupInProgress, 0); + } + } + + return PropertyCache.GetOrAdd(type, static t => + { + // Use explicit loops instead of LINQ to avoid allocations in hot path + var allProps = t.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + // First pass: count eligible properties + var eligibleCount = 0; + foreach (var p in allProps) + { + if (p.CanRead && p.GetIndexParameters().Length == 0) + { + eligibleCount++; + } + } + + // Second pass: fill result array + var result = new PropertyInfo[eligibleCount]; + var i = 0; + foreach (var p in allProps) + { + if (p.CanRead && p.GetIndexParameters().Length == 0) + { + result[i++] = p; + } + } + + return result; + }); + } + + /// + /// Clears the property cache. Called at end of test session to release memory. + /// + public static void ClearCache() + { + PropertyCache.Clear(); + } + + /// + /// Gets the current number of cached types. Useful for diagnostics. + /// + public static int CacheCount => PropertyCache.Count; +} diff --git a/TUnit.Core/EngineCancellationToken.cs b/TUnit.Core/EngineCancellationToken.cs index 3ed56c20b1..395cc7c5dd 100644 --- a/TUnit.Core/EngineCancellationToken.cs +++ b/TUnit.Core/EngineCancellationToken.cs @@ -33,6 +33,7 @@ internal void Initialise(CancellationToken cancellationToken) { #endif Console.CancelKeyPress += OnCancelKeyPress; + AppDomain.CurrentDomain.ProcessExit += OnProcessExit; #if NET5_0_OR_GREATER } #endif @@ -71,6 +72,21 @@ private void OnCancelKeyPress(object? sender, ConsoleCancelEventArgs e) e.Cancel = true; } + private void OnProcessExit(object? sender, EventArgs e) + { + // Process is exiting (SIGTERM, kill, etc.) - trigger cancellation to execute After hooks + // Note: ProcessExit runs on a background thread with limited time (~3 seconds on Windows) + // The After hooks registered via CancellationToken.Register() will execute when we cancel + if (!CancellationTokenSource.IsCancellationRequested) + { + CancellationTokenSource.Cancel(); + + // Give After hooks a brief moment to execute via registered callbacks + // ProcessExit has limited time, so we can only wait briefly + Task.Delay(TimeSpan.FromMilliseconds(500)).GetAwaiter().GetResult(); + } + } + /// /// Disposes the cancellation token source. /// @@ -82,6 +98,7 @@ public void Dispose() { #endif Console.CancelKeyPress -= OnCancelKeyPress; + AppDomain.CurrentDomain.ProcessExit -= OnProcessExit; #if NET5_0_OR_GREATER } #endif diff --git a/TUnit.Core/Enums/EventReceiverStage.cs b/TUnit.Core/Enums/EventReceiverStage.cs new file mode 100644 index 0000000000..2cb6e90286 --- /dev/null +++ b/TUnit.Core/Enums/EventReceiverStage.cs @@ -0,0 +1,17 @@ +namespace TUnit.Core.Enums; + +/// +/// Defines the execution stage for event receivers relative to instance-level hooks +/// +public enum EventReceiverStage +{ + /// + /// Execute before instance-level hooks ([Before(Test)], [After(Test)]) + /// + Early = 0, + + /// + /// Execute after instance-level hooks (default behavior for backward compatibility) + /// + Late = 1 +} diff --git a/TUnit.Core/Helpers/ClassConstructorHelper.cs b/TUnit.Core/Helpers/ClassConstructorHelper.cs index 136a6132b1..8430cfb63b 100644 --- a/TUnit.Core/Helpers/ClassConstructorHelper.cs +++ b/TUnit.Core/Helpers/ClassConstructorHelper.cs @@ -50,9 +50,9 @@ public static class ClassConstructorHelper return null; } - // Use the ClassConstructor to create the instance - var classConstructorType = classConstructorAttribute.ClassConstructorType; - var classConstructor = (IClassConstructor)Activator.CreateInstance(classConstructorType)!; + // Reuse existing ClassConstructor if already set, otherwise create new instance + var classConstructor = testBuilderContext.ClassConstructor + ?? (IClassConstructor)Activator.CreateInstance(classConstructorAttribute.ClassConstructorType)!; testBuilderContext.ClassConstructor = classConstructor; diff --git a/TUnit.Core/Helpers/Counter.cs b/TUnit.Core/Helpers/Counter.cs index d3df14af6f..e4ca4b759a 100644 --- a/TUnit.Core/Helpers/Counter.cs +++ b/TUnit.Core/Helpers/Counter.cs @@ -2,6 +2,11 @@ namespace TUnit.Core.Helpers; +/// +/// Thread-safe counter with event notification. +/// Captures event handler BEFORE state change to prevent race conditions +/// where subscribers miss notifications that occur during subscription. +/// [DebuggerDisplay("Count = {CurrentCount}")] public class Counter { @@ -11,34 +16,91 @@ public class Counter public int Increment() { + // Capture handler BEFORE state change to ensure all subscribers + // at the time of the change are notified (prevents TOCTOU race) + var handler = _onCountChanged; var newCount = Interlocked.Increment(ref _count); - var handler = _onCountChanged; - handler?.Invoke(this, newCount); + RaiseEventSafely(handler, newCount); return newCount; } public int Decrement() { + // Capture handler BEFORE state change to ensure all subscribers + // at the time of the change are notified (prevents TOCTOU race) + var handler = _onCountChanged; var newCount = Interlocked.Decrement(ref _count); - var handler = _onCountChanged; - handler?.Invoke(this, newCount); + RaiseEventSafely(handler, newCount); return newCount; } + /// + /// Adds a value to the counter. Use Increment/Decrement for single-step changes. + /// + /// The value to add (can be positive or negative). + /// The new count after the addition. + /// Thrown if the resulting count is negative, indicating a logic error. public int Add(int value) { + // Capture handler BEFORE state change to ensure all subscribers + // at the time of the change are notified (prevents TOCTOU race) + var handler = _onCountChanged; var newCount = Interlocked.Add(ref _count, value); - var handler = _onCountChanged; - handler?.Invoke(this, newCount); + // Guard against reference count going negative - indicates a bug in calling code + if (newCount < 0) + { + throw new InvalidOperationException( + $"Counter went below zero (result: {newCount}). This indicates a bug in the reference counting logic."); + } + + RaiseEventSafely(handler, newCount); return newCount; } + /// + /// Raises the event safely, ensuring all subscribers are notified even if some throw exceptions. + /// Collects all exceptions and throws AggregateException if any occurred. + /// + private void RaiseEventSafely(EventHandler? handler, int newCount) + { + if (handler == null) + { + return; + } + + var invocationList = handler.GetInvocationList(); + List? exceptions = null; + + foreach (var subscriber in invocationList) + { + try + { + ((EventHandler)subscriber).Invoke(this, newCount); + } + catch (Exception ex) + { + exceptions ??= []; + exceptions.Add(ex); + +#if DEBUG + Debug.WriteLine($"[Counter] Exception in OnCountChanged subscriber: {ex.Message}"); +#endif + } + } + + // If any subscribers threw, aggregate and rethrow after all are notified + if (exceptions?.Count > 0) + { + throw new AggregateException("One or more OnCountChanged subscribers threw an exception.", exceptions); + } + } + public int CurrentCount => Interlocked.CompareExchange(ref _count, 0, 0); public event EventHandler? OnCountChanged diff --git a/TUnit.Core/Helpers/DataSourceHelpers.cs b/TUnit.Core/Helpers/DataSourceHelpers.cs index d77e6aff47..fc7ba158e6 100644 --- a/TUnit.Core/Helpers/DataSourceHelpers.cs +++ b/TUnit.Core/Helpers/DataSourceHelpers.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using TUnit.Core.Interfaces; namespace TUnit.Core.Helpers; @@ -177,8 +178,9 @@ public static T InvokeIfFunc(object? value) // If it's a Func, invoke it first var actualData = InvokeIfFunc(data); - // Initialize the object if it implements IAsyncInitializer - await ObjectInitializer.InitializeAsync(actualData); + // During discovery, only IAsyncDiscoveryInitializer objects are initialized. + // Regular IAsyncInitializer objects are deferred to Execution phase. + await ObjectInitializer.InitializeForDiscoveryAsync(actualData); return actualData; } @@ -197,7 +199,8 @@ public static T InvokeIfFunc(object? value) if (enumerator.MoveNext()) { var value = enumerator.Current; - await ObjectInitializer.InitializeAsync(value); + // Discovery: only IAsyncDiscoveryInitializer + await ObjectInitializer.InitializeForDiscoveryAsync(value); return value; } @@ -224,14 +227,16 @@ public static T InvokeIfFunc(object? value) if (enumerator.MoveNext()) { var value = enumerator.Current; - await ObjectInitializer.InitializeAsync(value); + // Discovery: only IAsyncDiscoveryInitializer + await ObjectInitializer.InitializeForDiscoveryAsync(value); return value; } return null; } - // For non-enumerable types, just initialize and return - await ObjectInitializer.InitializeAsync(actualData); + // During discovery, only IAsyncDiscoveryInitializer objects are initialized. + // Regular IAsyncInitializer objects are deferred to Execution phase. + await ObjectInitializer.InitializeForDiscoveryAsync(actualData); return actualData; } @@ -561,6 +566,7 @@ public static void RegisterTypeCreator(Func> containingType, testInformation, dataSourceAttribute, + testSessionId, TestContext.Current, TestContext.Current?.Metadata.TestDetails.ClassInstance, TestContext.Current?.InternalEvents, @@ -578,8 +584,8 @@ public static void RegisterTypeCreator(Func> { var value = args[0]; - // Initialize the value if it implements IAsyncInitializer - await ObjectInitializer.InitializeAsync(value); + // Discovery: only IAsyncDiscoveryInitializer + await ObjectInitializer.InitializeForDiscoveryAsync(value); return value; } diff --git a/TUnit.Core/Helpers/Disposer.cs b/TUnit.Core/Helpers/Disposer.cs index 772b49de07..a70033bb79 100644 --- a/TUnit.Core/Helpers/Disposer.cs +++ b/TUnit.Core/Helpers/Disposer.cs @@ -1,9 +1,18 @@ -using TUnit.Core.Logging; +using TUnit.Core.Interfaces; +using TUnit.Core.Logging; namespace TUnit.Core.Helpers; -internal class Disposer(ILogger logger) +/// +/// Disposes objects asynchronously with logging. +/// Implements IDisposer for Dependency Inversion Principle compliance. +/// +internal class Disposer(ILogger logger) : IDisposer { + /// + /// Disposes an object and propagates any exceptions. + /// Exceptions are logged but NOT swallowed - callers must handle them. + /// public async ValueTask DisposeAsync(object? obj) { try @@ -19,10 +28,15 @@ public async ValueTask DisposeAsync(object? obj) } catch (Exception e) { + // Log the error for diagnostics if (logger != null) { await logger.LogErrorAsync(e); } + + // Propagate the exception - don't silently swallow disposal failures + // Callers can catch and aggregate if disposing multiple objects + throw; } } } diff --git a/TUnit.Core/Helpers/ParallelTaskHelper.cs b/TUnit.Core/Helpers/ParallelTaskHelper.cs new file mode 100644 index 0000000000..81f05f06d4 --- /dev/null +++ b/TUnit.Core/Helpers/ParallelTaskHelper.cs @@ -0,0 +1,165 @@ +namespace TUnit.Core.Helpers; + +/// +/// Helper methods for parallel task execution without LINQ allocations. +/// Provides optimized patterns for executing async operations in parallel. +/// Exceptions are aggregated in AggregateException when multiple tasks fail. +/// +public static class ParallelTaskHelper +{ + /// + /// Executes an async action for each item in an array, in parallel. + /// Uses pre-allocated task array to avoid LINQ allocations. + /// + /// The type of items to process. + /// The array of items to process. + /// The async action to execute for each item. + /// A task that completes when all items have been processed. + public static async Task ForEachAsync(T[] items, Func action) + { + if (items.Length == 0) + { + return; + } + + var tasks = new Task[items.Length]; + for (var i = 0; i < items.Length; i++) + { + tasks[i] = action(items[i]); + } + + await Task.WhenAll(tasks); + } + + /// + /// Executes an async action for each item in an array, in parallel, with cancellation support. + /// Uses pre-allocated task array to avoid LINQ allocations. + /// + /// The type of items to process. + /// The array of items to process. + /// The async action to execute for each item. + /// Token to cancel the operation. + /// A task that completes when all items have been processed. + public static async Task ForEachAsync(T[] items, Func action, CancellationToken cancellationToken) + { + if (items.Length == 0) + { + return; + } + + cancellationToken.ThrowIfCancellationRequested(); + + var tasks = new Task[items.Length]; + for (var i = 0; i < items.Length; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + tasks[i] = action(items[i], cancellationToken); + } + + await Task.WhenAll(tasks); + } + + /// + /// Executes an async action for each item in an array, in parallel, with an index. + /// Uses pre-allocated task array to avoid LINQ allocations. + /// + /// The type of items to process. + /// The array of items to process. + /// The async action to execute for each item with its index. + /// A task that completes when all items have been processed. + public static async Task ForEachWithIndexAsync(T[] items, Func action) + { + if (items.Length == 0) + { + return; + } + + var tasks = new Task[items.Length]; + for (var i = 0; i < items.Length; i++) + { + tasks[i] = action(items[i], i); + } + + await Task.WhenAll(tasks); + } + + /// + /// Executes an async action for each item in an array, in parallel, with an index and cancellation support. + /// Uses pre-allocated task array to avoid LINQ allocations. + /// + /// The type of items to process. + /// The array of items to process. + /// The async action to execute for each item with its index. + /// Token to cancel the operation. + /// A task that completes when all items have been processed. + public static async Task ForEachWithIndexAsync(T[] items, Func action, CancellationToken cancellationToken) + { + if (items.Length == 0) + { + return; + } + + cancellationToken.ThrowIfCancellationRequested(); + + var tasks = new Task[items.Length]; + for (var i = 0; i < items.Length; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + tasks[i] = action(items[i], i, cancellationToken); + } + + await Task.WhenAll(tasks); + } + + /// + /// Executes an async action for each item in a list, in parallel. + /// Uses pre-allocated task array to avoid LINQ allocations. + /// + /// The type of items to process. + /// The list of items to process. + /// The async action to execute for each item. + /// A task that completes when all items have been processed. + public static async Task ForEachAsync(IReadOnlyList items, Func action) + { + if (items.Count == 0) + { + return; + } + + var tasks = new Task[items.Count]; + for (var i = 0; i < items.Count; i++) + { + tasks[i] = action(items[i]); + } + + await Task.WhenAll(tasks); + } + + /// + /// Executes an async action for each item in a list, in parallel, with cancellation support. + /// Uses pre-allocated task array to avoid LINQ allocations. + /// + /// The type of items to process. + /// The list of items to process. + /// The async action to execute for each item. + /// Token to cancel the operation. + /// A task that completes when all items have been processed. + public static async Task ForEachAsync(IReadOnlyList items, Func action, CancellationToken cancellationToken) + { + if (items.Count == 0) + { + return; + } + + cancellationToken.ThrowIfCancellationRequested(); + + var tasks = new Task[items.Count]; + for (var i = 0; i < items.Count; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + tasks[i] = action(items[i], cancellationToken); + } + + await Task.WhenAll(tasks); + } +} diff --git a/TUnit.Core/Helpers/ReferenceEqualityComparer.cs b/TUnit.Core/Helpers/ReferenceEqualityComparer.cs index 2c26bfb5d1..16da75d77b 100644 --- a/TUnit.Core/Helpers/ReferenceEqualityComparer.cs +++ b/TUnit.Core/Helpers/ReferenceEqualityComparer.cs @@ -1,7 +1,34 @@ -namespace TUnit.Core.Helpers; +using System.Runtime.CompilerServices; -public class ReferenceEqualityComparer : IEqualityComparer +namespace TUnit.Core.Helpers; + +/// +/// Compares objects by reference identity, not value equality. +/// Uses RuntimeHelpers.GetHashCode to get identity-based hash codes. +/// +public sealed class ReferenceEqualityComparer : IEqualityComparer { + /// + /// Singleton instance to avoid repeated allocations. + /// + public static readonly ReferenceEqualityComparer Instance = new(); + + /// + /// Private constructor to enforce singleton pattern. + /// + private ReferenceEqualityComparer() + { + } + + /// + /// Compares two objects by reference identity. + /// + /// + /// The 'new' keyword is used because this method explicitly implements + /// IEqualityComparer<object>.Equals with nullable parameters, which + /// hides the inherited static Object.Equals(object?, object?) method. + /// This is intentional and provides the correct behavior for reference equality. + /// public new bool Equals(object? x, object? y) { return ReferenceEquals(x, y); @@ -9,6 +36,8 @@ public class ReferenceEqualityComparer : IEqualityComparer public int GetHashCode(object obj) { - return obj.GetHashCode(); + // Use RuntimeHelpers.GetHashCode for identity-based hash code + // This returns the same value as Object.GetHashCode() would if not overridden + return RuntimeHelpers.GetHashCode(obj); } } diff --git a/TUnit.Core/Interfaces/IAsyncDiscoveryInitializer.cs b/TUnit.Core/Interfaces/IAsyncDiscoveryInitializer.cs new file mode 100644 index 0000000000..8c9f4f1dfb --- /dev/null +++ b/TUnit.Core/Interfaces/IAsyncDiscoveryInitializer.cs @@ -0,0 +1,28 @@ +namespace TUnit.Core.Interfaces; + +/// +/// Defines a contract for types that require asynchronous initialization during test discovery. +/// +/// +/// +/// Unlike which runs during test execution, +/// implementations of this interface are initialized during the test discovery phase. +/// This enables data sources (such as InstanceMethodDataSource) to access +/// fully-initialized objects when generating test cases. +/// +/// +/// Common use cases include: +/// +/// Starting Docker containers before test case enumeration +/// Connecting to databases to discover parameterized test data +/// Initializing fixtures that provide data for test case generation +/// +/// +/// +/// This interface extends , meaning the same +/// method is used. The framework +/// guarantees exactly-once initialization semantics - objects will not be +/// re-initialized during test execution. +/// +/// +public interface IAsyncDiscoveryInitializer : IAsyncInitializer; diff --git a/TUnit.Core/Interfaces/IDisposer.cs b/TUnit.Core/Interfaces/IDisposer.cs new file mode 100644 index 0000000000..039665dbae --- /dev/null +++ b/TUnit.Core/Interfaces/IDisposer.cs @@ -0,0 +1,16 @@ +namespace TUnit.Core.Interfaces; + +/// +/// Interface for disposing objects. +/// Follows Dependency Inversion Principle - high-level modules depend on this abstraction. +/// +public interface IDisposer +{ + /// + /// Disposes an object asynchronously. + /// Implementations should propagate exceptions - callers handle aggregation. + /// + /// The object to dispose. + /// A task representing the disposal operation. + ValueTask DisposeAsync(object? obj); +} diff --git a/TUnit.Core/Interfaces/IInitializationCallback.cs b/TUnit.Core/Interfaces/IInitializationCallback.cs new file mode 100644 index 0000000000..4e6449dd6e --- /dev/null +++ b/TUnit.Core/Interfaces/IInitializationCallback.cs @@ -0,0 +1,33 @@ +using System.Collections.Concurrent; + +namespace TUnit.Core.Interfaces; + +/// +/// Defines a callback interface for object initialization during property injection. +/// +/// +/// +/// This interface is used to break circular dependencies between property injection +/// and initialization services. Property injectors can call back to the initialization +/// service without directly depending on it. +/// +/// +internal interface IInitializationCallback +{ + /// + /// Ensures an object is fully initialized (property injection + IAsyncInitializer). + /// + /// The type of object to initialize. + /// The object to initialize. + /// Shared object bag for the test context. + /// Method metadata for the test. Can be null. + /// Test context events for tracking. + /// A token to monitor for cancellation requests. + /// The initialized object. + ValueTask EnsureInitializedAsync( + T obj, + ConcurrentDictionary? objectBag = null, + MethodMetadata? methodMetadata = null, + TestContextEvents? events = null, + CancellationToken cancellationToken = default) where T : notnull; +} diff --git a/TUnit.Core/Interfaces/IObjectGraphDiscoverer.cs b/TUnit.Core/Interfaces/IObjectGraphDiscoverer.cs new file mode 100644 index 0000000000..8ac4586be3 --- /dev/null +++ b/TUnit.Core/Interfaces/IObjectGraphDiscoverer.cs @@ -0,0 +1,132 @@ +using System.Collections.Concurrent; + +namespace TUnit.Core.Interfaces; + +/// +/// Defines a contract for discovering object graphs from test contexts. +/// Pure query interface - only reads and returns data, does not modify state. +/// +/// +/// +/// Object graph discovery is used to find all objects that need initialization or disposal, +/// organized by their nesting depth in the object hierarchy. +/// +/// +/// The discoverer traverses: +/// +/// Test class constructor arguments +/// Test method arguments +/// Injected property values +/// Nested objects that implement +/// +/// +/// +/// For tracking operations that modify TestContext.TrackedObjects, see . +/// +/// +internal interface IObjectGraphDiscoverer +{ + /// + /// Discovers all objects from a test context, organized by depth level. + /// + /// The test context to discover objects from. + /// Optional cancellation token for long-running discovery. + /// + /// An containing all discovered objects organized by depth. + /// Depth 0 contains root objects (arguments and property values). + /// Higher depths contain nested objects. + /// + IObjectGraph DiscoverObjectGraph(TestContext testContext, CancellationToken cancellationToken = default); + + /// + /// Discovers nested objects from a single root object, organized by depth. + /// + /// The root object to discover nested objects from. + /// Optional cancellation token for long-running discovery. + /// + /// An containing all discovered objects organized by depth. + /// Depth 0 contains the root object itself. + /// Higher depths contain nested objects. + /// + IObjectGraph DiscoverNestedObjectGraph(object rootObject, CancellationToken cancellationToken = default); + + /// + /// Discovers objects and populates the test context's tracked objects dictionary directly. + /// Used for efficient object tracking without intermediate allocations. + /// + /// The test context to discover objects from and populate. + /// Optional cancellation token for long-running discovery. + /// + /// The tracked objects dictionary (same as testContext.TrackedObjects) populated with discovered objects. + /// + /// + /// This method modifies testContext.TrackedObjects directly. For pure query operations, + /// use instead. + /// + ConcurrentDictionary> DiscoverAndTrackObjects(TestContext testContext, CancellationToken cancellationToken = default); +} + +/// +/// Marker interface for object graph tracking operations. +/// Extends with operations that modify state. +/// +/// +/// +/// This interface exists to support Interface Segregation Principle: +/// clients that only need query operations can depend on , +/// while clients that need tracking can depend on . +/// +/// +/// Currently inherits all methods from . +/// The distinction exists for semantic clarity and future extensibility. +/// +/// +internal interface IObjectGraphTracker : IObjectGraphDiscoverer +{ + // All methods inherited from IObjectGraphDiscoverer + // This interface provides semantic clarity for tracking operations +} + +/// +/// Represents a discovered object graph organized by depth level. +/// +/// +/// Collections are exposed as read-only to prevent callers from corrupting internal state. +/// Use and for safe iteration. +/// +internal interface IObjectGraph +{ + /// + /// Gets objects organized by depth (0 = root arguments, 1+ = nested). + /// + /// + /// Returns a read-only view. Use for iteration. + /// + IReadOnlyDictionary> ObjectsByDepth { get; } + + /// + /// Gets all unique objects in the graph. + /// + /// + /// Returns a read-only view to prevent modification. + /// + IReadOnlyCollection AllObjects { get; } + + /// + /// Gets the maximum nesting depth (-1 if empty). + /// + int MaxDepth { get; } + + /// + /// Gets objects at a specific depth level. + /// + /// The depth level to retrieve objects from. + /// An enumerable of objects at the specified depth, or empty if none exist. + IEnumerable GetObjectsAtDepth(int depth); + + /// + /// Gets depth levels in descending order (deepest first). + /// + /// An enumerable of depth levels ordered from deepest to shallowest. + IEnumerable GetDepthsDescending(); +} diff --git a/TUnit.Core/Interfaces/IObjectInitializationService.cs b/TUnit.Core/Interfaces/IObjectInitializationService.cs new file mode 100644 index 0000000000..6e5eb70338 --- /dev/null +++ b/TUnit.Core/Interfaces/IObjectInitializationService.cs @@ -0,0 +1,67 @@ +namespace TUnit.Core.Interfaces; + +/// +/// Defines a contract for managing object initialization with phase awareness. +/// +/// +/// +/// This service provides thread-safe, deduplicated initialization of objects that implement +/// or . +/// +/// +/// The service supports two initialization phases: +/// +/// Discovery phase: Only objects are initialized +/// Execution phase: All objects are initialized +/// +/// +/// +internal interface IObjectInitializationService +{ + /// + /// Initializes an object during the execution phase. + /// + /// The object to initialize. If null or not an , no action is taken. + /// A token to monitor for cancellation requests. + /// A representing the asynchronous operation. + /// + /// + /// This method is thread-safe and ensures that each object is initialized exactly once. + /// Multiple concurrent calls for the same object will share the same initialization task. + /// + /// + ValueTask InitializeAsync(object? obj, CancellationToken cancellationToken = default); + + /// + /// Initializes an object during the discovery phase. + /// + /// The object to initialize. If null or not an , no action is taken. + /// A token to monitor for cancellation requests. + /// A representing the asynchronous operation. + /// + /// + /// Only objects implementing are initialized during discovery. + /// Regular objects are deferred to execution phase. + /// + /// + ValueTask InitializeForDiscoveryAsync(object? obj, CancellationToken cancellationToken = default); + + /// + /// Checks if an object has been successfully initialized. + /// + /// The object to check. + /// True if the object has been initialized successfully; otherwise, false. + /// + /// Returns false if the object is null, not an , + /// has not been initialized yet, or if initialization failed. + /// + bool IsInitialized(object? obj); + + /// + /// Clears the initialization cache. + /// + /// + /// Called at the end of a test session to release resources. + /// + void ClearCache(); +} diff --git a/TUnit.Core/Interfaces/ITestEndEventReceiver.cs b/TUnit.Core/Interfaces/ITestEndEventReceiver.cs index 5ee6af1b62..9693c2804b 100644 --- a/TUnit.Core/Interfaces/ITestEndEventReceiver.cs +++ b/TUnit.Core/Interfaces/ITestEndEventReceiver.cs @@ -1,5 +1,7 @@ namespace TUnit.Core.Interfaces; +using TUnit.Core.Enums; + /// /// Simplified interface for test end event receivers /// @@ -9,4 +11,20 @@ public interface ITestEndEventReceiver : IEventReceiver /// Called when a test ends /// ValueTask OnTestEnd(TestContext context); + + /// + /// Gets the execution stage of this event receiver relative to instance-level hooks. + /// + /// + /// Early stage executes before [After(Test)] hooks, Late stage executes after. + /// Default is Late for backward compatibility. + /// This property is only available on .NET 8.0+ due to default interface member requirements. + /// On older frameworks, all receivers execute at Late stage. + /// + /// + /// The execution stage. Default is . + /// +#if NET + public EventReceiverStage Stage => EventReceiverStage.Late; +#endif } diff --git a/TUnit.Core/Interfaces/ITestStartEventReceiver.cs b/TUnit.Core/Interfaces/ITestStartEventReceiver.cs index 858039d339..6fb6670f4e 100644 --- a/TUnit.Core/Interfaces/ITestStartEventReceiver.cs +++ b/TUnit.Core/Interfaces/ITestStartEventReceiver.cs @@ -1,5 +1,7 @@ namespace TUnit.Core.Interfaces; +using TUnit.Core.Enums; + /// /// Simplified interface for test start event receivers /// @@ -9,4 +11,20 @@ public interface ITestStartEventReceiver : IEventReceiver /// Called when a test starts /// ValueTask OnTestStart(TestContext context); + + /// + /// Gets the execution stage of this event receiver relative to instance-level hooks. + /// + /// + /// Early stage executes before [Before(Test)] hooks, Late stage executes after. + /// Default is Late for backward compatibility. + /// This property is only available on .NET 8.0+ due to default interface member requirements. + /// On older frameworks, all receivers execute at Late stage. + /// + /// + /// The execution stage. Default is . + /// +#if NET + public EventReceiverStage Stage => EventReceiverStage.Late; +#endif } diff --git a/TUnit.Core/Interfaces/SourceGenerator/PropertyInjectionMetadata.cs b/TUnit.Core/Interfaces/SourceGenerator/PropertyInjectionMetadata.cs index d4c8309fc3..bea4f11a9f 100644 --- a/TUnit.Core/Interfaces/SourceGenerator/PropertyInjectionMetadata.cs +++ b/TUnit.Core/Interfaces/SourceGenerator/PropertyInjectionMetadata.cs @@ -24,7 +24,7 @@ public sealed class PropertyInjectionMetadata /// /// Gets the type that contains the property (the parent class). /// - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] public required Type ContainingType { get; init; } /// diff --git a/TUnit.Core/Models/TestBuildContext.cs b/TUnit.Core/Models/TestBuildContext.cs new file mode 100644 index 0000000000..df706ce0c4 --- /dev/null +++ b/TUnit.Core/Models/TestBuildContext.cs @@ -0,0 +1,45 @@ +namespace TUnit.Core; + +/// +/// Context for capturing output during test building and data source initialization. +/// This context is active during the test building phase, before TestContext is created. +/// Output captured here is transferred to the TestContext when it's created. +/// +public sealed class TestBuildContext : Context, IDisposable +{ + private static readonly AsyncLocal _current = new(); + + public static new TestBuildContext? Current + { + get => _current.Value; + internal set => _current.Value = value; + } + + public TestBuildContext() : base(null) + { + } + + /// + /// Gets the captured standard output during test building. + /// + public string GetCapturedOutput() => GetStandardOutput(); + + /// + /// Gets the captured error output during test building. + /// + public string GetCapturedErrorOutput() => GetErrorOutput(); + + internal override void SetAsyncLocalContext() + { + Current = this; + } + + /// + /// Clears the current TestBuildContext. + /// + public new void Dispose() + { + Current = null; + base.Dispose(); + } +} diff --git a/TUnit.Core/ObjectInitializer.cs b/TUnit.Core/ObjectInitializer.cs index 362445816e..46c52923e8 100644 --- a/TUnit.Core/ObjectInitializer.cs +++ b/TUnit.Core/ObjectInitializer.cs @@ -1,52 +1,131 @@ -using System.Runtime.CompilerServices; +using System.Collections.Concurrent; +using TUnit.Core.Helpers; using TUnit.Core.Interfaces; +using TUnit.Core.Services; namespace TUnit.Core; -public static class ObjectInitializer +/// +/// Static facade for initializing objects that implement . +/// Provides thread-safe, deduplicated initialization with explicit phase control. +/// +/// +/// +/// Use during test discovery - only objects are initialized. +/// Use during test execution - all objects are initialized. +/// +/// +/// For dependency injection scenarios, use directly. +/// +/// +internal static class ObjectInitializer { - private static readonly ConditionalWeakTable _initializationTasks = new(); - private static readonly Lock _lock = new(); + // Use Lazy pattern to ensure InitializeAsync is called exactly once per object, + // even under contention. GetOrAdd's factory can be called multiple times, but with + // Lazy + ExecutionAndPublication mode, only one initialization actually runs. + private static readonly ConcurrentDictionary> InitializationTasks = + new(Helpers.ReferenceEqualityComparer.Instance); - internal static bool IsInitialized(object? obj) + /// + /// Initializes an object during the discovery phase. + /// Only objects implementing IAsyncDiscoveryInitializer are initialized. + /// Regular IAsyncInitializer objects are skipped (deferred to execution phase). + /// Thread-safe with deduplication - safe to call multiple times. + /// + /// The object to potentially initialize. + /// Cancellation token. + internal static ValueTask InitializeForDiscoveryAsync(object? obj, CancellationToken cancellationToken = default) { - if (obj is not IAsyncInitializer) + // During discovery, only initialize IAsyncDiscoveryInitializer + if (obj is not IAsyncDiscoveryInitializer asyncDiscoveryInitializer) { - return false; + return default; } - lock (_lock) + return InitializeCoreAsync(obj, asyncDiscoveryInitializer, cancellationToken); + } + + /// + /// Initializes an object during the execution phase. + /// All objects implementing IAsyncInitializer are initialized. + /// Thread-safe with deduplication - safe to call multiple times. + /// + /// The object to potentially initialize. + /// Cancellation token. + internal static ValueTask InitializeAsync(object? obj, CancellationToken cancellationToken = default) + { + if (obj is not IAsyncInitializer asyncInitializer) { - return _initializationTasks.TryGetValue(obj, out var task) && task.IsCompleted; + return default; } + + return InitializeCoreAsync(obj, asyncInitializer, cancellationToken); } - public static async ValueTask InitializeAsync(object? obj, CancellationToken cancellationToken = default) + /// + /// Checks if an object has been successfully initialized by ObjectInitializer. + /// + /// The object to check. + /// True if the object has been initialized successfully; otherwise, false. + /// + /// Returns false if the object is null, not an , + /// has not been initialized yet, or if initialization failed. + /// + internal static bool IsInitialized(object? obj) { - if (obj is IAsyncInitializer asyncInitializer) + if (obj is not IAsyncInitializer) { - await GetInitializationTask(obj, asyncInitializer, cancellationToken); + return false; } + + // Use Status == RanToCompletion to ensure we don't return true for faulted/canceled tasks + // (IsCompletedSuccessfully is not available in netstandard2.0) + // With Lazy, we need to check if the Lazy has a value AND that value completed successfully + return InitializationTasks.TryGetValue(obj, out var lazyTask) && + lazyTask.IsValueCreated && + lazyTask.Value.Status == TaskStatus.RanToCompletion; } - private static async Task GetInitializationTask(object obj, IAsyncInitializer asyncInitializer, CancellationToken cancellationToken) + /// + /// Clears the initialization cache. + /// + /// + /// Called at the end of a test session to release resources. + /// + internal static void ClearCache() { - Task initializationTask; + InitializationTasks.Clear(); + } - lock (_lock) + private static async ValueTask InitializeCoreAsync( + object obj, + IAsyncInitializer asyncInitializer, + CancellationToken cancellationToken) + { + // Use Lazy with ExecutionAndPublication mode to ensure InitializeAsync + // is called exactly once, even under contention. GetOrAdd's factory may be + // called multiple times, but Lazy ensures only one initialization runs. + var lazyTask = InitializationTasks.GetOrAdd(obj, + _ => new Lazy( + () => asyncInitializer.InitializeAsync(), + LazyThreadSafetyMode.ExecutionAndPublication)); + + try { - if (_initializationTasks.TryGetValue(obj, out var existingTask)) - { - initializationTask = existingTask; - } - else - { - initializationTask = asyncInitializer.InitializeAsync(); - _initializationTasks.Add(obj, initializationTask); - } + // Wait for initialization with cancellation support + await lazyTask.Value.WaitAsync(cancellationToken); + } + catch (OperationCanceledException) + { + // Propagate cancellation without modification + throw; + } + catch + { + // Remove failed initialization from cache to allow retry + // This is important for transient failures that may succeed on retry + InitializationTasks.TryRemove(obj, out _); + throw; } - - // Wait for initialization with cancellation support - await initializationTask.WaitAsync(cancellationToken); } } diff --git a/TUnit.Core/PropertyInjection/Initialization/PropertyInitializationContext.cs b/TUnit.Core/PropertyInjection/Initialization/PropertyInitializationContext.cs index 12aef89941..85d4f6ae80 100644 --- a/TUnit.Core/PropertyInjection/Initialization/PropertyInitializationContext.cs +++ b/TUnit.Core/PropertyInjection/Initialization/PropertyInitializationContext.cs @@ -7,6 +7,7 @@ namespace TUnit.Core.PropertyInjection.Initialization; /// /// Encapsulates all context needed for property initialization. /// Follows Single Responsibility Principle by being a pure data container. +/// Provides factory methods to reduce duplication when creating contexts (DRY). /// internal sealed class PropertyInitializationContext { @@ -84,4 +85,124 @@ internal sealed class PropertyInitializationContext /// Parent object for nested properties. /// public object? ParentInstance { get; init; } + + #region Factory Methods (DRY) + + /// + /// Creates a context for source-generated property injection. + /// + public static PropertyInitializationContext ForSourceGenerated( + object instance, + PropertyInjectionMetadata metadata, + ConcurrentDictionary objectBag, + MethodMetadata? methodMetadata, + TestContextEvents events, + ConcurrentDictionary visitedObjects, + TestContext? testContext, + bool isNestedProperty = false) + { + return new PropertyInitializationContext + { + Instance = instance, + SourceGeneratedMetadata = metadata, + PropertyName = metadata.PropertyName, + PropertyType = metadata.PropertyType, + PropertySetter = metadata.SetProperty, + ObjectBag = objectBag, + MethodMetadata = methodMetadata, + Events = events, + VisitedObjects = visitedObjects, + TestContext = testContext, + IsNestedProperty = isNestedProperty + }; + } + + /// + /// Creates a context for reflection-based property injection. + /// + public static PropertyInitializationContext ForReflection( + object instance, + PropertyInfo property, + IDataSourceAttribute dataSource, + Action propertySetter, + ConcurrentDictionary objectBag, + MethodMetadata? methodMetadata, + TestContextEvents events, + ConcurrentDictionary visitedObjects, + TestContext? testContext, + bool isNestedProperty = false) + { + return new PropertyInitializationContext + { + Instance = instance, + PropertyInfo = property, + DataSource = dataSource, + PropertyName = property.Name, + PropertyType = property.PropertyType, + PropertySetter = propertySetter, + ObjectBag = objectBag, + MethodMetadata = methodMetadata, + Events = events, + VisitedObjects = visitedObjects, + TestContext = testContext, + IsNestedProperty = isNestedProperty + }; + } + + /// + /// Creates a context for caching during registration (uses placeholder instance). + /// + public static PropertyInitializationContext ForCaching( + PropertyInjectionMetadata metadata, + ConcurrentDictionary objectBag, + MethodMetadata? methodMetadata, + TestContextEvents events, + TestContext testContext) + { + return new PropertyInitializationContext + { + Instance = PlaceholderInstance.Instance, + SourceGeneratedMetadata = metadata, + PropertyName = metadata.PropertyName, + PropertyType = metadata.PropertyType, + PropertySetter = metadata.SetProperty, + ObjectBag = objectBag, + MethodMetadata = methodMetadata, + Events = events, + VisitedObjects = new ConcurrentDictionary(), + TestContext = testContext, + IsNestedProperty = false + }; + } + + /// + /// Creates a context for reflection caching during registration (uses placeholder instance). + /// + public static PropertyInitializationContext ForReflectionCaching( + PropertyInfo property, + IDataSourceAttribute dataSource, + Action propertySetter, + ConcurrentDictionary objectBag, + MethodMetadata? methodMetadata, + TestContextEvents events, + TestContext testContext) + { + return new PropertyInitializationContext + { + Instance = PlaceholderInstance.Instance, + PropertyInfo = property, + DataSource = dataSource, + PropertyName = property.Name, + PropertyType = property.PropertyType, + PropertySetter = propertySetter, + ObjectBag = objectBag, + MethodMetadata = methodMetadata, + Events = events, + VisitedObjects = new ConcurrentDictionary(), + TestContext = testContext, + IsNestedProperty = false + }; + } + + #endregion } \ No newline at end of file diff --git a/TUnit.Core/PropertyInjection/PropertyCacheKeyGenerator.cs b/TUnit.Core/PropertyInjection/PropertyCacheKeyGenerator.cs new file mode 100644 index 0000000000..c98abadadb --- /dev/null +++ b/TUnit.Core/PropertyInjection/PropertyCacheKeyGenerator.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using TUnit.Core.Interfaces.SourceGenerator; + +namespace TUnit.Core.PropertyInjection; + +/// +/// Generates consistent cache keys for property injection values. +/// Centralizes cache key generation to ensure consistency across the codebase (DRY principle). +/// +/// +/// Cache keys are formatted as "{DeclaringTypeName}.{PropertyName}" to uniquely identify +/// properties across different types. This format is used for storing and retrieving +/// injected property values in test contexts. +/// +public static class PropertyCacheKeyGenerator +{ + /// + /// Generates a cache key from source-generated property metadata. + /// + /// The property injection metadata from source generation. + /// A unique cache key string for the property. + public static string GetCacheKey(PropertyInjectionMetadata metadata) + { + return $"{metadata.ContainingType.FullName}.{metadata.PropertyName}"; + } + + /// + /// Generates a cache key from a PropertyInfo (reflection-based properties). + /// + /// The PropertyInfo from reflection. + /// A unique cache key string for the property. + public static string GetCacheKey(PropertyInfo property) + { + return $"{property.DeclaringType!.FullName}.{property.Name}"; + } +} diff --git a/TUnit.Core/PropertyInjection/PropertyHelper.cs b/TUnit.Core/PropertyInjection/PropertyHelper.cs index e5cd934d66..a32d24a7a7 100644 --- a/TUnit.Core/PropertyInjection/PropertyHelper.cs +++ b/TUnit.Core/PropertyInjection/PropertyHelper.cs @@ -11,13 +11,18 @@ internal static class PropertyHelper { /// /// Gets PropertyInfo in an AOT-safe manner. + /// Searches for both public and non-public properties to support internal properties. /// public static PropertyInfo GetPropertyInfo( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type containingType, string propertyName) { - var property = containingType.GetProperty(propertyName); + // Use binding flags to find both public and non-public properties + // This is necessary to support internal properties on internal classes + var property = containingType.GetProperty( + propertyName, + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); if (property == null) { diff --git a/TUnit.Core/PropertyInjection/PropertyInjectionCache.cs b/TUnit.Core/PropertyInjection/PropertyInjectionCache.cs index c883dfc858..b94917c014 100644 --- a/TUnit.Core/PropertyInjection/PropertyInjectionCache.cs +++ b/TUnit.Core/PropertyInjection/PropertyInjectionCache.cs @@ -1,24 +1,22 @@ -using System.Diagnostics.CodeAnalysis; -using TUnit.Core.Data; +using TUnit.Core.Data; namespace TUnit.Core.PropertyInjection; /// -/// Provides caching functionality for property injection operations. -/// Follows Single Responsibility Principle by focusing only on caching. +/// Provides pure caching functionality for property injection metadata. +/// Follows Single Responsibility Principle - only caches type metadata, no execution logic. /// /// This cache supports both execution modes: /// - Source Generation Mode: Uses pre-compiled property setters and metadata /// - Reflection Mode: Uses runtime discovery and dynamic property access /// -/// The IL2067 suppressions are necessary because types come from runtime objects -/// (via GetType() calls) which cannot have compile-time annotations. +/// Instance-level injection tracking has been moved to ObjectLifecycleService +/// to maintain SRP (caching vs execution are separate concerns). /// internal static class PropertyInjectionCache { private static readonly ThreadSafeDictionary _injectionPlans = new(); private static readonly ThreadSafeDictionary _shouldInjectCache = new(); - private static readonly ThreadSafeDictionary> _injectionTasks = new(); /// /// Gets or creates an injection plan for the specified type. @@ -41,42 +39,4 @@ public static bool HasInjectableProperties(Type type) return plan.HasProperties; }); } - - /// - /// Ensures properties are injected into the specified instance. - /// Fast-path optimized for already-injected instances (zero allocation). - /// - public static async ValueTask EnsureInjectedAsync(object instance, Func injectionFactory) - { - if (_injectionTasks.TryGetValue(instance, out var existingTcs) && existingTcs.Task.IsCompleted) - { - if (existingTcs.Task.IsFaulted) - { - await existingTcs.Task.ConfigureAwait(false); - } - - return; - } - - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - existingTcs = _injectionTasks.GetOrAdd(instance, _ => tcs); - - if (existingTcs == tcs) - { - try - { - await injectionFactory(instance).ConfigureAwait(false); - tcs.SetResult(true); - } - catch (Exception ex) - { - tcs.SetException(ex); - throw; - } - } - else - { - await existingTcs.Task.ConfigureAwait(false); - } - } } diff --git a/TUnit.Core/PropertyInjection/PropertyInjectionPlanBuilder.cs b/TUnit.Core/PropertyInjection/PropertyInjectionPlanBuilder.cs index a0cf5ee323..9c950da9c5 100644 --- a/TUnit.Core/PropertyInjection/PropertyInjectionPlanBuilder.cs +++ b/TUnit.Core/PropertyInjection/PropertyInjectionPlanBuilder.cs @@ -10,6 +10,22 @@ namespace TUnit.Core.PropertyInjection; /// internal static class PropertyInjectionPlanBuilder { + /// + /// Walks up the inheritance chain from the given type to typeof(object), + /// invoking the action for each type in the hierarchy. + /// + /// The starting type. + /// The action to invoke for each type in the inheritance chain. + private static void WalkInheritanceChain(Type type, Action action) + { + var currentType = type; + while (currentType != null && currentType != typeof(object)) + { + action(currentType); + currentType = currentType.BaseType; + } + } + /// /// Creates an injection plan for source-generated mode. /// Walks the inheritance chain to include all injectable properties from base classes. @@ -20,8 +36,7 @@ public static PropertyInjectionPlan BuildSourceGeneratedPlan(Type type) var processedProperties = new HashSet(); // Walk up the inheritance chain to find all properties with data sources - var currentType = type; - while (currentType != null && currentType != typeof(object)) + WalkInheritanceChain(type, currentType => { var propertySource = PropertySourceRegistry.GetSource(currentType); if (propertySource?.ShouldInitialize == true) @@ -35,9 +50,7 @@ public static PropertyInjectionPlan BuildSourceGeneratedPlan(Type type) } } } - - currentType = currentType.BaseType; - } + }); var sourceGenProps = allProperties.ToArray(); @@ -62,8 +75,7 @@ public static PropertyInjectionPlan BuildReflectionPlan(Type type) var processedProperties = new HashSet(); // Walk up the inheritance chain to find all properties with data source attributes - var currentType = type; - while (currentType != null && currentType != typeof(object)) + WalkInheritanceChain(type, currentType => { var properties = currentType.GetProperties( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly) @@ -87,9 +99,7 @@ public static PropertyInjectionPlan BuildReflectionPlan(Type type) } } } - - currentType = currentType.BaseType; - } + }); return new PropertyInjectionPlan { @@ -102,18 +112,39 @@ public static PropertyInjectionPlan BuildReflectionPlan(Type type) /// /// Builds an injection plan based on the current execution mode. + /// Falls back to reflection when source-gen mode has no registered source for a type. + /// This handles generic types like ErrFixture<MyType> where the source generator + /// couldn't register a property source for the closed generic type. /// - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Source gen mode has its own path>")] + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Source gen mode has its own path")] public static PropertyInjectionPlan Build(Type type) { - return SourceRegistrar.IsEnabled - ? BuildSourceGeneratedPlan(type) - : BuildReflectionPlan(type); + if (!SourceRegistrar.IsEnabled) + { + return BuildReflectionPlan(type); + } + + // Try source-generated plan first + var plan = BuildSourceGeneratedPlan(type); + + // If no properties found in source-gen mode, fall back to reflection + // This handles generic types that couldn't be registered at compile time + if (!plan.HasProperties) + { + var reflectionPlan = BuildReflectionPlan(type); + if (reflectionPlan.HasProperties) + { + return reflectionPlan; + } + } + + return plan; } } /// /// Represents a plan for injecting properties into an object. +/// Provides iterator methods to abstract source-gen vs reflection branching (DRY). /// internal sealed class PropertyInjectionPlan { @@ -121,4 +152,100 @@ internal sealed class PropertyInjectionPlan public required PropertyInjectionMetadata[] SourceGeneratedProperties { get; init; } public required (PropertyInfo Property, IDataSourceAttribute DataSource)[] ReflectionProperties { get; init; } public required bool HasProperties { get; init; } + + /// + /// Iterates over all properties in the plan, abstracting source-gen vs reflection. + /// Call the appropriate callback based on which mode has properties. + /// + /// Action to invoke for each source-generated property. + /// Action to invoke for each reflection property. + public void ForEachProperty( + Action onSourceGenerated, + Action<(PropertyInfo Property, IDataSourceAttribute DataSource)> onReflection) + { + if (SourceGeneratedProperties.Length > 0) + { + foreach (var metadata in SourceGeneratedProperties) + { + onSourceGenerated(metadata); + } + } + else if (ReflectionProperties.Length > 0) + { + foreach (var prop in ReflectionProperties) + { + onReflection(prop); + } + } + } + + /// + /// Iterates over all properties in the plan asynchronously. + /// + public async Task ForEachPropertyAsync( + Func onSourceGenerated, + Func<(PropertyInfo Property, IDataSourceAttribute DataSource), Task> onReflection) + { + if (SourceGeneratedProperties.Length > 0) + { + foreach (var metadata in SourceGeneratedProperties) + { + await onSourceGenerated(metadata); + } + } + else if (ReflectionProperties.Length > 0) + { + foreach (var prop in ReflectionProperties) + { + await onReflection(prop); + } + } + } + + /// + /// Executes actions for all properties in parallel. + /// + public Task ForEachPropertyParallelAsync( + Func onSourceGenerated, + Func<(PropertyInfo Property, IDataSourceAttribute DataSource), Task> onReflection) + { + if (SourceGeneratedProperties.Length > 0) + { + return Helpers.ParallelTaskHelper.ForEachAsync(SourceGeneratedProperties, onSourceGenerated); + } + else if (ReflectionProperties.Length > 0) + { + return Helpers.ParallelTaskHelper.ForEachAsync(ReflectionProperties, onReflection); + } + + return Task.CompletedTask; + } + + /// + /// Gets property values from an instance, abstracting source-gen vs reflection. + /// + public IEnumerable GetPropertyValues(object instance) + { + if (SourceGeneratedProperties.Length > 0) + { + foreach (var metadata in SourceGeneratedProperties) + { + var property = metadata.ContainingType.GetProperty(metadata.PropertyName); + if (property?.CanRead == true) + { + yield return property.GetValue(instance); + } + } + } + else if (ReflectionProperties.Length > 0) + { + foreach (var (property, _) in ReflectionProperties) + { + if (property.CanRead) + { + yield return property.GetValue(instance); + } + } + } + } } diff --git a/TUnit.Core/PropertyInjection/PropertySetterFactory.cs b/TUnit.Core/PropertyInjection/PropertySetterFactory.cs index 3d9dce651d..8bc1662cdf 100644 --- a/TUnit.Core/PropertyInjection/PropertySetterFactory.cs +++ b/TUnit.Core/PropertyInjection/PropertySetterFactory.cs @@ -1,21 +1,56 @@ -using System.Diagnostics.CodeAnalysis; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace TUnit.Core.PropertyInjection; /// -/// Factory for creating property setters. +/// Factory for creating property setters with caching for performance. /// Consolidates all property setter creation logic in one place following DRY principle. /// +/// +/// Setters are cached using the PropertyInfo as the key to avoid repeated reflection calls. +/// This significantly improves performance when the same property is accessed multiple times +/// (e.g., in test retries or shared test data scenarios). +/// internal static class PropertySetterFactory { + // Cache setters per PropertyInfo to avoid repeated reflection + private static readonly ConcurrentDictionary> SetterCache = new(); + + /// + /// Gets or creates a setter delegate for the given property. + /// Uses caching to avoid repeated reflection calls. + /// + #if NET6_0_OR_GREATER + [RequiresUnreferencedCode("Backing field access for init-only properties requires reflection")] + #endif + public static Action GetOrCreateSetter(PropertyInfo property) + { + return SetterCache.GetOrAdd(property, CreateSetterCore); + } + /// /// Creates a setter delegate for the given property. + /// Consider using for better performance through caching. /// #if NET6_0_OR_GREATER [RequiresUnreferencedCode("Backing field access for init-only properties requires reflection")] #endif public static Action CreateSetter(PropertyInfo property) + { + // Delegate to cached version for consistency + return GetOrCreateSetter(property); + } + + /// + /// Core implementation for creating a setter delegate. + /// Called by GetOrCreateSetter for caching. + /// + #if NET6_0_OR_GREATER + [RequiresUnreferencedCode("Backing field access for init-only properties requires reflection")] + #endif + private static Action CreateSetterCore(PropertyInfo property) { if (property.CanWrite && property.SetMethod != null) { diff --git a/TUnit.Core/Services/ObjectInitializationService.cs b/TUnit.Core/Services/ObjectInitializationService.cs new file mode 100644 index 0000000000..a18845001c --- /dev/null +++ b/TUnit.Core/Services/ObjectInitializationService.cs @@ -0,0 +1,40 @@ +using TUnit.Core.Interfaces; + +namespace TUnit.Core.Services; + +/// +/// Thread-safe service for initializing objects that implement . +/// Provides deduplicated initialization with explicit phase control. +/// +/// +/// +/// This service delegates to the static to ensure consistent +/// behavior and avoid duplicate caches. This consolidates initialization tracking in one place. +/// +/// +internal sealed class ObjectInitializationService : IObjectInitializationService +{ + /// + /// Creates a new instance of the initialization service. + /// + public ObjectInitializationService() + { + // No local cache needed - delegates to static ObjectInitializer + } + + /// + public ValueTask InitializeForDiscoveryAsync(object? obj, CancellationToken cancellationToken = default) + => ObjectInitializer.InitializeForDiscoveryAsync(obj, cancellationToken); + + /// + public ValueTask InitializeAsync(object? obj, CancellationToken cancellationToken = default) + => ObjectInitializer.InitializeAsync(obj, cancellationToken); + + /// + public bool IsInitialized(object? obj) + => ObjectInitializer.IsInitialized(obj); + + /// + public void ClearCache() + => ObjectInitializer.ClearCache(); +} diff --git a/TUnit.Core/TUnit.Core.targets b/TUnit.Core/TUnit.Core.targets index 6dd52bbb6b..fd6824ed49 100644 --- a/TUnit.Core/TUnit.Core.targets +++ b/TUnit.Core/TUnit.Core.targets @@ -8,7 +8,7 @@ - <_TUnitPolyfillVersion>8.9.1 + <_TUnitPolyfillVersion>9.3.4 <_TUnitNeedsPolyfill Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netstandard2.1' or '$(TargetFrameworkIdentifier)' == '.NETFramework'">true diff --git a/TUnit.Core/TestBuilderContext.cs b/TUnit.Core/TestBuilderContext.cs index cac0481205..f6ec4b0eef 100644 --- a/TUnit.Core/TestBuilderContext.cs +++ b/TUnit.Core/TestBuilderContext.cs @@ -49,7 +49,8 @@ public void RegisterForInitialization(object? obj) { Events.OnInitialize += async (sender, args) => { - await ObjectInitializer.InitializeAsync(obj); + // Discovery: only IAsyncDiscoveryInitializer + await ObjectInitializer.InitializeForDiscoveryAsync(obj); }; } @@ -57,7 +58,11 @@ internal static TestBuilderContext FromTestContext(TestContext testContext, IDat { return new TestBuilderContext { - Events = testContext.InternalEvents, TestMetadata = testContext.Metadata.TestDetails.MethodMetadata, DataSourceAttribute = dataSourceAttribute, StateBag = testContext.StateBag.Items, + Events = testContext.InternalEvents, + TestMetadata = testContext.Metadata.TestDetails.MethodMetadata, + DataSourceAttribute = dataSourceAttribute, + StateBag = testContext.StateBag.Items, + ClassConstructor = testContext.ClassConstructor, }; } } diff --git a/TUnit.Core/TestContext.Output.cs b/TUnit.Core/TestContext.Output.cs index 6d41e43787..8f2223eacc 100644 --- a/TUnit.Core/TestContext.Output.cs +++ b/TUnit.Core/TestContext.Output.cs @@ -32,7 +32,7 @@ void ITestOutput.AttachArtifact(Artifact artifact) } string ITestOutput.GetStandardOutput() => GetOutput(); - string ITestOutput.GetErrorOutput() => GetErrorOutput(); + string ITestOutput.GetErrorOutput() => GetOutputError(); void ITestOutput.WriteLine(string message) { @@ -46,7 +46,47 @@ void ITestOutput.WriteError(string message) _errorWriter.WriteLine(message); } - internal string GetOutput() => _outputWriter?.ToString() ?? string.Empty; + /// + /// Gets the combined build-time and execution-time standard output. + /// + public override string GetStandardOutput() + { + return GetOutput(); + } + + /// + /// Gets the combined build-time and execution-time error output. + /// + public override string GetErrorOutput() + { + return GetOutputError(); + } + + internal string GetOutput() + { + var buildOutput = _buildTimeOutput ?? string.Empty; + var baseOutput = base.GetStandardOutput(); // Get output from base class (Context) + var writerOutput = _outputWriter?.ToString() ?? string.Empty; - internal new string GetErrorOutput() => _errorWriter?.ToString() ?? string.Empty; + // Combine all three sources: build-time, base class output, and writer output + var parts = new[] { buildOutput, baseOutput, writerOutput } + .Where(s => !string.IsNullOrEmpty(s)) + .ToArray(); + + return parts.Length == 0 ? string.Empty : string.Join(Environment.NewLine, parts); + } + + internal string GetOutputError() + { + var buildErrorOutput = _buildTimeErrorOutput ?? string.Empty; + var baseErrorOutput = base.GetErrorOutput(); // Get error output from base class (Context) + var writerErrorOutput = _errorWriter?.ToString() ?? string.Empty; + + // Combine all three sources: build-time error, base class error output, and writer error output + var parts = new[] { buildErrorOutput, baseErrorOutput, writerErrorOutput } + .Where(s => !string.IsNullOrEmpty(s)) + .ToArray(); + + return parts.Length == 0 ? string.Empty : string.Join(Environment.NewLine, parts); + } } diff --git a/TUnit.Core/TestContext.cs b/TUnit.Core/TestContext.cs index 8abb2dbb1e..6ef95724de 100644 --- a/TUnit.Core/TestContext.cs +++ b/TUnit.Core/TestContext.cs @@ -49,12 +49,16 @@ public TestContext(string testName, IServiceProvider serviceProvider, ClassHookC private static readonly AsyncLocal TestContexts = new(); - internal static readonly Dictionary> InternalParametersDictionary = new(); + // Use ConcurrentDictionary for thread-safe access during parallel test discovery + internal static readonly ConcurrentDictionary> InternalParametersDictionary = new(); private StringWriter? _outputWriter; private StringWriter? _errorWriter; + private string? _buildTimeOutput; + private string? _buildTimeErrorOutput; + public static new TestContext? Current { get => TestContexts.Value; @@ -69,7 +73,21 @@ internal set public static IReadOnlyDictionary> Parameters => InternalParametersDictionary; - public static IConfiguration Configuration { get; internal set; } = null!; + private static IConfiguration? _configuration; + + /// + /// Gets the test configuration. Throws a descriptive exception if accessed before initialization. + /// + /// Thrown if Configuration is accessed before the test engine initializes it. + public static IConfiguration Configuration + { + get => _configuration ?? throw new InvalidOperationException( + "TestContext.Configuration has not been initialized. " + + "This property is only available after the TUnit test engine has started. " + + "If you are accessing this from a static constructor or field initializer, " + + "consider moving the code to a test setup method or test body instead."); + internal set => _configuration = value; + } public static string? OutputDirectory { @@ -135,6 +153,12 @@ internal override void SetAsyncLocalContext() internal bool RunOnTestDiscovery { get; set; } + /// + /// Indicates whether this test is reusing the discovery-time instance instead of creating a new instance. + /// When true, property resolution and initialization should be skipped since the instance is already prepared. + /// + internal bool IsDiscoveryInstanceReused { get; set; } + public object Lock { get; } = new(); @@ -149,8 +173,21 @@ internal override void SetAsyncLocalContext() internal AbstractExecutableTest InternalExecutableTest { get; set; } = null!; private ConcurrentDictionary>? _trackedObjects; - internal ConcurrentDictionary> TrackedObjects => - _trackedObjects ??= new(); + /// + /// Thread-safe lazy initialization of TrackedObjects using LazyInitializer + /// to prevent race conditions when multiple threads access this property simultaneously. + /// + internal ConcurrentDictionary> TrackedObjects => + LazyInitializer.EnsureInitialized(ref _trackedObjects)!; + /// + /// Sets the output captured during test building phase. + /// This output is prepended to the test's execution output. + /// + internal void SetBuildTimeOutput(string? output, string? errorOutput) + { + _buildTimeOutput = output; + _buildTimeErrorOutput = errorOutput; + } } diff --git a/TUnit.Core/Tracking/ObjectTracker.cs b/TUnit.Core/Tracking/ObjectTracker.cs index 8e702a6262..bb273255c5 100644 --- a/TUnit.Core/Tracking/ObjectTracker.cs +++ b/TUnit.Core/Tracking/ObjectTracker.cs @@ -7,19 +7,105 @@ namespace TUnit.Core.Tracking; /// /// Pure reference counting object tracker for disposable objects. /// Objects are disposed when their reference count reaches zero, regardless of sharing type. +/// Uses ReferenceEqualityComparer to track objects by identity, not value equality. /// +/// +/// The static s_trackedObjects dictionary is shared across all tests. +/// Call at the end of a test session to release memory. +/// internal class ObjectTracker(TrackableObjectGraphProvider trackableObjectGraphProvider, Disposer disposer) { - private static readonly ConcurrentDictionary _trackedObjects = new(); + // Use ReferenceEqualityComparer to prevent objects with custom Equals from sharing state + private static readonly ConcurrentDictionary s_trackedObjects = + new(Helpers.ReferenceEqualityComparer.Instance); + + // Lock for atomic decrement-check-dispose operations to prevent race conditions + private static readonly object s_disposalLock = new(); + + // Collects errors from async disposal callbacks for post-session review + private static readonly ConcurrentBag s_asyncCallbackErrors = new(); + + /// + /// Gets any errors that occurred during async disposal callbacks. + /// Check this at the end of a test session to surface hidden failures. + /// + public static IReadOnlyCollection GetAsyncCallbackErrors() => s_asyncCallbackErrors.ToArray(); + + /// + /// Clears all static tracking state. Call at the end of a test session to release memory. + /// + public static void ClearStaticTracking() + { + s_trackedObjects.Clear(); + s_asyncCallbackErrors.Clear(); + } + + /// + /// Gets an existing counter for the object or creates a new one. + /// Centralizes the GetOrAdd pattern to ensure consistent counter creation. + /// + private static Counter GetOrCreateCounter(object obj) => + s_trackedObjects.GetOrAdd(obj, static _ => new Counter()); + + /// + /// Flattens a ConcurrentDictionary of depth-keyed HashSets into a single HashSet. + /// Thread-safe: locks each HashSet while copying. + /// Pre-calculates capacity to avoid HashSet resizing during population. + /// + private static HashSet FlattenTrackedObjects(ConcurrentDictionary> trackedObjects) + { +#if NETSTANDARD2_0 + // .NET Standard 2.0 doesn't support HashSet capacity constructor + var result = new HashSet(Helpers.ReferenceEqualityComparer.Instance); +#else + // First pass: calculate total capacity to avoid resizing + var totalCapacity = 0; + foreach (var kvp in trackedObjects) + { + lock (kvp.Value) + { + totalCapacity += kvp.Value.Count; + } + } + + // Second pass: populate with pre-sized HashSet + var result = new HashSet(totalCapacity, Helpers.ReferenceEqualityComparer.Instance); +#endif + foreach (var kvp in trackedObjects) + { + lock (kvp.Value) + { + foreach (var obj in kvp.Value) + { + result.Add(obj); + } + } + } + + return result; + } public void TrackObjects(TestContext testContext) { - var alreadyTracked = testContext.TrackedObjects.SelectMany(x => x.Value).ToHashSet(); + // Get already tracked objects (DRY: use helper method) + var alreadyTracked = FlattenTrackedObjects(testContext.TrackedObjects); - var newTrackableObjects = trackableObjectGraphProvider.GetTrackableObjects(testContext) - .SelectMany(x => x.Value) - .Except(alreadyTracked) - .ToHashSet(); + // Get new trackable objects + var newTrackableObjects = new HashSet(Helpers.ReferenceEqualityComparer.Instance); + var trackableDict = trackableObjectGraphProvider.GetTrackableObjects(testContext); + foreach (var kvp in trackableDict) + { + lock (kvp.Value) + { + foreach (var obj in kvp.Value) + { + if (!alreadyTracked.Contains(obj)) + { + newTrackableObjects.Add(obj); + } + } + } + } foreach (var obj in newTrackableObjects) { @@ -29,9 +115,10 @@ public void TrackObjects(TestContext testContext) public async ValueTask UntrackObjects(TestContext testContext, List cleanupExceptions) { - foreach (var obj in testContext.TrackedObjects - .SelectMany(x => x.Value) - .ToHashSet()) + // Get all objects to untrack (DRY: use helper method) + var objectsToUntrack = FlattenTrackedObjects(testContext.TrackedObjects); + + foreach (var obj in objectsToUntrack) { try { @@ -70,7 +157,7 @@ private void TrackObject(object? obj) return; } - var counter = _trackedObjects.GetOrAdd(obj, static _ => new Counter()); + var counter = GetOrCreateCounter(obj); counter.Increment(); } @@ -81,20 +168,36 @@ private async ValueTask UntrackObject(object? obj) return; } - if (_trackedObjects.TryGetValue(obj, out var counter)) - { - var count = counter.Decrement(); + var shouldDispose = false; - if (count < 0) + // Use lock to make decrement-check-remove atomic and prevent race conditions + // where multiple tests could try to dispose the same object simultaneously + lock (s_disposalLock) + { + if (s_trackedObjects.TryGetValue(obj, out var counter)) { - throw new InvalidOperationException("Reference count for object went below zero. This indicates a bug in the reference counting logic."); - } + var count = counter.Decrement(); - if (count == 0) - { - await disposer.DisposeAsync(obj).ConfigureAwait(false); + if (count < 0) + { + throw new InvalidOperationException("Reference count for object went below zero. This indicates a bug in the reference counting logic."); + } + + if (count == 0) + { + // Remove from tracking dictionary to prevent memory leak + // Use TryRemove to ensure atomicity - only remove if still in dictionary + s_trackedObjects.TryRemove(obj, out _); + shouldDispose = true; + } } } + + // Dispose outside the lock to avoid blocking other untrack operations + if (shouldDispose) + { + await disposer.DisposeAsync(obj).ConfigureAwait(false); + } } /// @@ -105,37 +208,128 @@ private static bool ShouldSkipTracking(object? obj) return obj is not IDisposable and not IAsyncDisposable; } + /// + /// Registers a callback to be invoked when the object is disposed (ref count reaches 0). + /// If the object is already disposed (or was never tracked), the callback is invoked immediately. + /// The callback is guaranteed to be invoked exactly once (idempotent). + /// + /// The object to monitor for disposal. If null or not disposable, the method returns without action. + /// The callback to invoke on disposal. Must not be null. + /// Thrown when is null. public static void OnDisposed(object? o, Action action) { - if(o is not IDisposable and not IAsyncDisposable) +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(action); +#else + if (action == null) { - return; + throw new ArgumentNullException(nameof(action)); } +#endif - _trackedObjects.GetOrAdd(o, static _ => new Counter()) - .OnCountChanged += (_, count) => - { - if (count == 0) - { - action(); - } - }; + RegisterDisposalCallback(o, action, static a => a()); } + /// + /// Registers an async callback to be invoked when the object is disposed (ref count reaches 0). + /// If the object is already disposed (or was never tracked), the callback is invoked immediately. + /// The callback is guaranteed to be invoked exactly once (idempotent). + /// + /// The object to monitor for disposal. If null or not disposable, the method returns without action. + /// The async callback to invoke on disposal. Must not be null. + /// Thrown when is null. public static void OnDisposedAsync(object? o, Func asyncAction) { - if(o is not IDisposable and not IAsyncDisposable) +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(asyncAction); +#else + if (asyncAction == null) + { + throw new ArgumentNullException(nameof(asyncAction)); + } +#endif + + // Wrap async action in fire-and-forget with exception collection + RegisterDisposalCallback(o, asyncAction, static a => _ = SafeExecuteAsync(a)); + } + + /// + /// Core implementation for registering disposal callbacks. + /// Extracts common logic from OnDisposed and OnDisposedAsync (DRY principle). + /// + /// The type of action (Action or Func<Task>). + /// The object to monitor for disposal. + /// The callback action. + /// How to invoke the action (sync vs async wrapper). + private static void RegisterDisposalCallback( + object? o, + TAction action, + Action invoker) + where TAction : Delegate + { + if (o is not IDisposable and not IAsyncDisposable) { return; } - _trackedObjects.GetOrAdd(o, static _ => new Counter()) - .OnCountChanged += async (_, count) => + // Only register callback if the object is actually being tracked. + // If not tracked, invoke callback immediately (object is effectively "disposed"). + // This prevents creating spurious counters for untracked objects. + if (!s_trackedObjects.TryGetValue(o, out var counter)) { - if (count == 0) + // Object not tracked - invoke callback immediately + invoker(action); + return; + } + + // Use flag to ensure callback only fires once (idempotent) + var invoked = 0; + EventHandler? handler = null; + + handler = (sender, count) => + { + if (count == 0 && Interlocked.Exchange(ref invoked, 1) == 0) { - await asyncAction().ConfigureAwait(false); + // Remove handler to prevent memory leaks + if (sender is Counter c && handler != null) + { + c.OnCountChanged -= handler; + } + + invoker(action); } }; + + counter.OnCountChanged += handler; + + // Check if already disposed (count is 0) - invoke immediately if so + // This prevents lost callbacks when registering after disposal + // Idempotent check ensures this doesn't double-fire if event already triggered + if (counter.CurrentCount == 0 && Interlocked.Exchange(ref invoked, 1) == 0) + { + counter.OnCountChanged -= handler; + invoker(action); + } + } + + /// + /// Executes an async action safely, catching and collecting exceptions + /// for post-session review instead of silently swallowing them. + /// + private static async Task SafeExecuteAsync(Func asyncAction) + { + try + { + await asyncAction().ConfigureAwait(false); + } + catch (Exception ex) + { + // Collect error for post-session review instead of silently swallowing + s_asyncCallbackErrors.Add(ex); + +#if DEBUG + System.Diagnostics.Debug.WriteLine($"[ObjectTracker] Exception in OnDisposedAsync callback: {ex.Message}"); +#endif + } } } diff --git a/TUnit.Core/Tracking/TrackableObjectGraphProvider.cs b/TUnit.Core/Tracking/TrackableObjectGraphProvider.cs index 97301e53f8..2460406c6f 100644 --- a/TUnit.Core/Tracking/TrackableObjectGraphProvider.cs +++ b/TUnit.Core/Tracking/TrackableObjectGraphProvider.cs @@ -1,57 +1,47 @@ -using System.Collections.Concurrent; -using System.Diagnostics.CodeAnalysis; -using TUnit.Core.PropertyInjection; +using System.Collections.Concurrent; +using TUnit.Core.Discovery; +using TUnit.Core.Interfaces; using TUnit.Core.StaticProperties; namespace TUnit.Core.Tracking; +/// +/// Provides trackable objects from test contexts for lifecycle management. +/// Delegates to for the actual discovery logic. +/// internal class TrackableObjectGraphProvider { - public ConcurrentDictionary> GetTrackableObjects(TestContext testContext) - { - var visitedObjects = testContext.TrackedObjects; - - var testDetails = testContext.Metadata.TestDetails; - - foreach (var classArgument in testDetails.TestClassArguments) - { - if (classArgument != null && visitedObjects.GetOrAdd(0, []).Add(classArgument)) - { - AddNestedTrackableObjects(classArgument, visitedObjects, 1); - } - } + private readonly IObjectGraphDiscoverer _discoverer; - foreach (var methodArgument in testDetails.TestMethodArguments) - { - if (methodArgument != null && visitedObjects.GetOrAdd(0, []).Add(methodArgument)) - { - AddNestedTrackableObjects(methodArgument, visitedObjects, 1); - } - } - - foreach (var property in testDetails.TestClassInjectedPropertyArguments.Values) - { - if (property != null && visitedObjects.GetOrAdd(0, []).Add(property)) - { - AddNestedTrackableObjects(property, visitedObjects, 1); - } - } + /// + /// Creates a new instance with the default discoverer. + /// + public TrackableObjectGraphProvider() : this(new ObjectGraphDiscoverer()) + { + } - return visitedObjects; + /// + /// Creates a new instance with a custom discoverer (for testing). + /// + public TrackableObjectGraphProvider(IObjectGraphDiscoverer discoverer) + { + _discoverer = discoverer; } - private static void AddToLevel(Dictionary> objectsByLevel, int level, object obj) + /// + /// Gets trackable objects from a test context, organized by depth level. + /// Delegates to the shared IObjectGraphDiscoverer to eliminate code duplication. + /// + /// The test context to get trackable objects from. + /// Optional cancellation token for long-running discovery. + public ConcurrentDictionary> GetTrackableObjects(TestContext testContext, CancellationToken cancellationToken = default) { - if (!objectsByLevel.TryGetValue(level, out var list)) - { - list = []; - objectsByLevel[level] = list; - } - list.Add(obj); + // OCP-compliant: Use the interface method directly instead of type-checking + return _discoverer.DiscoverAndTrackObjects(testContext, cancellationToken); } /// - /// Get trackable objects for static properties (session-level) + /// Gets trackable objects for static properties (session-level). /// public IEnumerable GetStaticPropertyTrackableObjects() { @@ -63,67 +53,4 @@ public IEnumerable GetStaticPropertyTrackableObjects() } } } - - private void AddNestedTrackableObjects(object obj, ConcurrentDictionary> visitedObjects, int currentDepth) - { - var plan = PropertyInjectionCache.GetOrCreatePlan(obj.GetType()); - - if(!SourceRegistrar.IsEnabled) - { - foreach (var prop in plan.ReflectionProperties) - { - var value = prop.Property.GetValue(obj); - - if (value == null) - { - continue; - } - - // Check if already visited before yielding to prevent duplicates - if (!visitedObjects.GetOrAdd(currentDepth, []).Add(value)) - { - continue; - } - - if (!PropertyInjectionCache.HasInjectableProperties(value.GetType())) - { - continue; - } - - AddNestedTrackableObjects(value, visitedObjects, currentDepth + 1); - } - } - else - { - foreach (var metadata in plan.SourceGeneratedProperties) - { - var property = metadata.ContainingType.GetProperty(metadata.PropertyName); - - if (property == null || !property.CanRead) - { - continue; - } - - var value = property.GetValue(obj); - - if (value == null) - { - continue; - } - - // Check if already visited before yielding to prevent duplicates - if (!visitedObjects.GetOrAdd(currentDepth, []).Add(value)) - { - continue; - } - - if (!PropertyInjectionCache.HasInjectableProperties(value.GetType())) - { - continue; - } - - AddNestedTrackableObjects(value, visitedObjects, currentDepth + 1); - } - } - } } diff --git a/TUnit.Engine.Tests/Attributes/SkipNetFrameworkAttribute.cs b/TUnit.Engine.Tests/Attributes/SkipNetFrameworkAttribute.cs index 8ad1fb4e2b..b8205c3ad6 100644 --- a/TUnit.Engine.Tests/Attributes/SkipNetFrameworkAttribute.cs +++ b/TUnit.Engine.Tests/Attributes/SkipNetFrameworkAttribute.cs @@ -2,7 +2,7 @@ public class SkipNetFrameworkAttribute(string reason) : SkipAttribute(reason) { - private static readonly string NetVersion = Environment.GetEnvironmentVariable("NET_VERSION") ?? "net9.0"; + private static readonly string NetVersion = Environment.GetEnvironmentVariable("NET_VERSION") ?? "net10.0"; public override Task ShouldSkip(TestRegisteredContext testRegisteredContext) { diff --git a/TUnit.Engine.Tests/CancellationAfterHooksTests.cs b/TUnit.Engine.Tests/CancellationAfterHooksTests.cs new file mode 100644 index 0000000000..750bb9ef38 --- /dev/null +++ b/TUnit.Engine.Tests/CancellationAfterHooksTests.cs @@ -0,0 +1,122 @@ +using Shouldly; +using TUnit.Engine.Tests.Enums; +using TUnit.Engine.Tests.Extensions; + +namespace TUnit.Engine.Tests; + +/// +/// Validates that After hooks execute even when tests are cancelled (Issue #3882). +/// These tests run the cancellation test scenarios and verify that After hooks created marker files. +/// +public class CancellationAfterHooksTests(TestMode testMode) : InvokableTestBase(testMode) +{ + private static readonly string TempPath = Path.GetTempPath(); + + [Test] + public async Task TestLevel_AfterHook_Runs_OnCancellation() + { + var afterMarkerFile = Path.Combine(TempPath, "TUnit_3882_Tests", "after_Test_ThatGets_Cancelled.txt"); + + // Clean up any existing marker files + if (File.Exists(afterMarkerFile)) + { + File.Delete(afterMarkerFile); + } + + await RunTestsWithFilter( + "/*/*/CancellationAfterHooksTests/*", + [ + // Test run completes even though the test itself fails (timeout is expected) + result => result.ResultSummary.Counters.Total.ShouldBe(1), + // Test should fail due to timeout + result => result.ResultSummary.Counters.Failed.ShouldBe(1), + // After hook should have created the marker file - this proves After hooks ran on cancellation + _ => File.Exists(afterMarkerFile).ShouldBeTrue($"After hook marker file should exist at {afterMarkerFile}") + ]); + + // Verify marker file content + if (File.Exists(afterMarkerFile)) + { + var content = await File.ReadAllTextAsync(afterMarkerFile); + content.ShouldContain("After hook executed"); + } + } + + [Test] + public async Task SessionLevel_AfterHook_Runs_OnCancellation() + { + var afterMarkerFile = Path.Combine(TempPath, "TUnit_3882_Session_After.txt"); + + // Clean up any existing marker files + if (File.Exists(afterMarkerFile)) + { + File.Delete(afterMarkerFile); + } + + await RunTestsWithFilter( + "/*/*/SessionLevelCancellationTests/*", + [ + // After Session hook should have created the marker file - this proves Session After hooks ran on cancellation + _ => File.Exists(afterMarkerFile).ShouldBeTrue($"Session After hook marker file should exist at {afterMarkerFile}") + ]); + + // Verify marker file content + if (File.Exists(afterMarkerFile)) + { + var content = await File.ReadAllTextAsync(afterMarkerFile); + content.ShouldContain("Session After hook executed"); + } + } + + [Test] + public async Task AssemblyLevel_AfterHook_Runs_OnCancellation() + { + var afterMarkerFile = Path.Combine(TempPath, "TUnit_3882_Assembly_After.txt"); + + // Clean up any existing marker files + if (File.Exists(afterMarkerFile)) + { + File.Delete(afterMarkerFile); + } + + await RunTestsWithFilter( + "/*/*/AssemblyLevelCancellationTests/*", + [ + // After Assembly hook should have created the marker file - this proves Assembly After hooks ran on cancellation + _ => File.Exists(afterMarkerFile).ShouldBeTrue($"Assembly After hook marker file should exist at {afterMarkerFile}") + ]); + + // Verify marker file content + if (File.Exists(afterMarkerFile)) + { + var content = await File.ReadAllTextAsync(afterMarkerFile); + content.ShouldContain("Assembly After hook executed"); + } + } + + [Test] + public async Task ClassLevel_AfterHook_Runs_OnCancellation() + { + var afterMarkerFile = Path.Combine(TempPath, "TUnit_3882_Class_After.txt"); + + // Clean up any existing marker files + if (File.Exists(afterMarkerFile)) + { + File.Delete(afterMarkerFile); + } + + await RunTestsWithFilter( + "/*/*/ClassLevelCancellationTests/*", + [ + // After Class hook should have created the marker file - this proves Class After hooks ran on cancellation + _ => File.Exists(afterMarkerFile).ShouldBeTrue($"Class After hook marker file should exist at {afterMarkerFile}") + ]); + + // Verify marker file content + if (File.Exists(afterMarkerFile)) + { + var content = await File.ReadAllTextAsync(afterMarkerFile); + content.ShouldContain("Class After hook executed"); + } + } +} diff --git a/TUnit.Engine.Tests/ExternalCancellationTests.cs b/TUnit.Engine.Tests/ExternalCancellationTests.cs new file mode 100644 index 0000000000..88dce62572 --- /dev/null +++ b/TUnit.Engine.Tests/ExternalCancellationTests.cs @@ -0,0 +1,147 @@ +using System.Diagnostics; +using CliWrap; +using Shouldly; +using TUnit.Engine.Tests.Enums; + +namespace TUnit.Engine.Tests; + +/// +/// Validates that After hooks execute even when tests are cancelled EXTERNALLY (Issue #3882). +/// These tests start the test process asynchronously, cancel it mid-execution (simulating Ctrl+C or Stop button), +/// and verify that After hooks still execute by checking for marker files. +/// +public class ExternalCancellationTests(TestMode testMode) : InvokableTestBase(testMode) +{ + private static readonly string TempPath = Path.GetTempPath(); + private static readonly string GetEnvironmentVariable = Environment.GetEnvironmentVariable("NET_VERSION") ?? "net10.0"; + + /// + /// Runs a test with external cancellation (simulates Ctrl+C, VS Test Explorer Stop button). + /// + /// Test filter pattern + /// Path to the marker file that proves After hook executed + /// Expected content in the marker file + private async Task RunTestWithExternalCancellation(string filter, string markerFile, string expectedMarkerContent) + { + // Clean up any existing marker files + if (File.Exists(markerFile)) + { + File.Delete(markerFile); + } + + var testProject = Sourcy.DotNet.Projects.TUnit_TestProject; + var guid = Guid.NewGuid().ToString("N"); + var trxFilename = guid + ".trx"; + + using var gracefulCancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + using var forcefulCancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(25)); + + // Use cross-platform executable detection (Linux: no extension, Windows: .exe) + var binDir = new DirectoryInfo(Path.Combine(testProject.DirectoryName!, "bin", "Release", GetEnvironmentVariable)); + var file = binDir.GetFiles("TUnit.TestProject").FirstOrDefault()?.FullName + ?? binDir.GetFiles("TUnit.TestProject.exe").First().FullName; + + var command = testMode switch + { + TestMode.SourceGenerated => Cli.Wrap(file) + .WithArguments( + [ + "--treenode-filter", filter, + "--report-trx", "--report-trx-filename", trxFilename, + "--diagnostic-verbosity", "Debug", + "--diagnostic", "--diagnostic-file-prefix", $"log_ExternalCancellation_{GetType().Name}_", + ]) + .WithWorkingDirectory(testProject.DirectoryName!) + .WithValidation(CommandResultValidation.None), + + TestMode.Reflection => Cli.Wrap(file) + .WithArguments( + [ + "--treenode-filter", filter, + "--report-trx", "--report-trx-filename", trxFilename, + "--diagnostic-verbosity", "Debug", + "--diagnostic", "--diagnostic-file-prefix", $"log_ExternalCancellation_{GetType().Name}_", + "--reflection" + ]) + .WithWorkingDirectory(testProject.DirectoryName!) + .WithValidation(CommandResultValidation.None), + + // Skip AOT and SingleFile modes for external cancellation (only test in CI) + TestMode.AOT => null, + TestMode.SingleFileApplication => null, + _ => throw new ArgumentOutOfRangeException(nameof(testMode), testMode, null) + }; + + // Skip AOT and SingleFile modes + if (command == null) + { + return; + } + + try + { + await command.ExecuteAsync(forcefulCancellationTokenSource.Token, gracefulCancellationTokenSource.Token); + } + catch (OperationCanceledException) + { + // Expected: Process was cancelled via CancellationToken + Console.WriteLine("[ExternalCancellation] Process cancelled successfully (expected)"); + } + catch (Exception ex) + { + // Log unexpected exceptions but don't fail - After hooks might still execute + Console.WriteLine($"[ExternalCancellation] Unexpected exception: {ex.Message}"); + } + + // Verify marker file exists - this proves After hook executed even on external cancellation + File.Exists(markerFile).ShouldBeTrue($"After hook marker file should exist at {markerFile}"); + + // Verify marker file content + var content = await File.ReadAllTextAsync(markerFile); + content.ShouldContain(expectedMarkerContent); + } + + [Test] + public async Task TestLevel_AfterHook_Runs_OnExternalCancellation() + { + var afterMarkerFile = Path.Combine(TempPath, "TUnit_3882_External", "after_Test_ThatGets_Cancelled_Externally.txt"); + + await RunTestWithExternalCancellation( + "/*/*/ExternalCancellationTests/*", + afterMarkerFile, + "After hook executed"); + } + + [Test] + public async Task SessionLevel_AfterHook_Runs_OnExternalCancellation() + { + var afterMarkerFile = Path.Combine(TempPath, "TUnit_3882_External_Session_After.txt"); + + await RunTestWithExternalCancellation( + "/*/*/ExternalSessionLevelCancellationTests/*", + afterMarkerFile, + "Session After hook executed"); + } + + [Test] + public async Task AssemblyLevel_AfterHook_Runs_OnExternalCancellation() + { + var afterMarkerFile = Path.Combine(TempPath, "TUnit_3882_External_Assembly_After.txt"); + + await RunTestWithExternalCancellation( + "/*/*/ExternalAssemblyLevelCancellationTests/*", + afterMarkerFile, + "Assembly After hook executed"); + } + + [Test] + public async Task ClassLevel_AfterHook_Runs_OnExternalCancellation() + { + var afterMarkerFile = Path.Combine(TempPath, "TUnit_3882_External_Class_After.txt"); + + await RunTestWithExternalCancellation( + "/*/*/ExternalClassLevelCancellationTests/*", + afterMarkerFile, + "Class After hook executed"); + } +} diff --git a/TUnit.Engine.Tests/InvokableTestBase.cs b/TUnit.Engine.Tests/InvokableTestBase.cs index bc20a0e4a1..1e264449bc 100644 --- a/TUnit.Engine.Tests/InvokableTestBase.cs +++ b/TUnit.Engine.Tests/InvokableTestBase.cs @@ -22,7 +22,7 @@ public static IEnumerable GetTestModes() } } - private static readonly string GetEnvironmentVariable = Environment.GetEnvironmentVariable("NET_VERSION") ?? "net9.0"; + private static readonly string GetEnvironmentVariable = Environment.GetEnvironmentVariable("NET_VERSION") ?? "net10.0"; public static bool IsNetFramework => GetEnvironmentVariable.StartsWith("net4"); diff --git a/TUnit.Engine.Tests/JUnitReporterTests.cs b/TUnit.Engine.Tests/JUnitReporterTests.cs new file mode 100644 index 0000000000..9320db65d6 --- /dev/null +++ b/TUnit.Engine.Tests/JUnitReporterTests.cs @@ -0,0 +1,120 @@ +using Microsoft.Testing.Platform.Extensions; +using TUnit.Core; +using TUnit.Engine.Reporters; + +namespace TUnit.Engine.Tests; + +[NotInParallel] +public class JUnitReporterTests +{ + private sealed class MockExtension : IExtension + { + public string Uid => "MockExtension"; + public string DisplayName => "Mock"; + public string Version => "1.0.0"; + public string Description => "Mock Extension"; + public Task IsEnabledAsync() => Task.FromResult(true); + } + + [After(Test)] + public void Cleanup() + { + // Clean up environment variables after each test + Environment.SetEnvironmentVariable("TUNIT_DISABLE_JUNIT_REPORTER", null); + Environment.SetEnvironmentVariable("TUNIT_ENABLE_JUNIT_REPORTER", null); + Environment.SetEnvironmentVariable("GITLAB_CI", null); + Environment.SetEnvironmentVariable("CI_SERVER", null); + Environment.SetEnvironmentVariable("JUNIT_XML_OUTPUT_PATH", null); + } + + [Test] + public async Task IsEnabledAsync_Should_Return_False_When_TUNIT_DISABLE_JUNIT_REPORTER_Is_Set() + { + // Arrange + Environment.SetEnvironmentVariable("TUNIT_DISABLE_JUNIT_REPORTER", "true"); + Environment.SetEnvironmentVariable("GITLAB_CI", "true"); // Even with GitLab CI, should be disabled + var extension = new MockExtension(); + var reporter = new JUnitReporter(extension); + + // Act + var isEnabled = await reporter.IsEnabledAsync(); + + // Assert + await Assert.That(isEnabled).IsFalse(); + } + + [Test] + public async Task IsEnabledAsync_Should_Return_True_When_GITLAB_CI_Is_Set() + { + // Arrange + Environment.SetEnvironmentVariable("GITLAB_CI", "true"); + var extension = new MockExtension(); + var reporter = new JUnitReporter(extension); + + // Act + var isEnabled = await reporter.IsEnabledAsync(); + + // Assert + await Assert.That(isEnabled).IsTrue(); + } + + [Test] + public async Task IsEnabledAsync_Should_Return_True_When_CI_SERVER_Is_Set() + { + // Arrange + Environment.SetEnvironmentVariable("CI_SERVER", "yes"); + var extension = new MockExtension(); + var reporter = new JUnitReporter(extension); + + // Act + var isEnabled = await reporter.IsEnabledAsync(); + + // Assert + await Assert.That(isEnabled).IsTrue(); + } + + [Test] + public async Task IsEnabledAsync_Should_Return_True_When_TUNIT_ENABLE_JUNIT_REPORTER_Is_Set() + { + // Arrange + Environment.SetEnvironmentVariable("TUNIT_ENABLE_JUNIT_REPORTER", "true"); + var extension = new MockExtension(); + var reporter = new JUnitReporter(extension); + + // Act + var isEnabled = await reporter.IsEnabledAsync(); + + // Assert + await Assert.That(isEnabled).IsTrue(); + } + + [Test] + public async Task IsEnabledAsync_Should_Return_False_When_No_Environment_Variables_Are_Set() + { + // Arrange + var extension = new MockExtension(); + var reporter = new JUnitReporter(extension); + + // Act + var isEnabled = await reporter.IsEnabledAsync(); + + // Assert + await Assert.That(isEnabled).IsFalse(); + } + + [Test] + public async Task IsEnabledAsync_Should_Prefer_Disable_Over_Enable() + { + // Arrange + Environment.SetEnvironmentVariable("TUNIT_DISABLE_JUNIT_REPORTER", "true"); + Environment.SetEnvironmentVariable("TUNIT_ENABLE_JUNIT_REPORTER", "true"); + var extension = new MockExtension(); + var reporter = new JUnitReporter(extension); + + // Act + var isEnabled = await reporter.IsEnabledAsync(); + + // Assert + await Assert.That(isEnabled).IsFalse(); + } +} diff --git a/TUnit.Engine.Tests/TUnit.Engine.Tests.csproj b/TUnit.Engine.Tests/TUnit.Engine.Tests.csproj index 4349b07740..99a1694787 100644 --- a/TUnit.Engine.Tests/TUnit.Engine.Tests.csproj +++ b/TUnit.Engine.Tests/TUnit.Engine.Tests.csproj @@ -3,7 +3,7 @@ - net9.0 + net10.0 diff --git a/TUnit.Engine/Building/Interfaces/ITestBuilder.cs b/TUnit.Engine/Building/Interfaces/ITestBuilder.cs index c50bda428e..8f75be2a0e 100644 --- a/TUnit.Engine/Building/Interfaces/ITestBuilder.cs +++ b/TUnit.Engine/Building/Interfaces/ITestBuilder.cs @@ -14,8 +14,9 @@ internal interface ITestBuilder /// The test metadata /// The test data /// + /// Whether this test is reusing the discovery instance /// An executable test ready for execution - Task BuildTestAsync(TestMetadata metadata, TestBuilder.TestData testData, TestBuilderContext testBuilderContext); + Task BuildTestAsync(TestMetadata metadata, TestBuilder.TestData testData, TestBuilderContext testBuilderContext, bool isReusingDiscoveryInstance = false); /// /// Builds all executable tests from a single TestMetadata using its DataCombinationGenerator delegate. diff --git a/TUnit.Engine/Building/TestBuilder.cs b/TUnit.Engine/Building/TestBuilder.cs index c6c3cb787a..a8ebc2afbc 100644 --- a/TUnit.Engine/Building/TestBuilder.cs +++ b/TUnit.Engine/Building/TestBuilder.cs @@ -21,8 +21,7 @@ internal sealed class TestBuilder : ITestBuilder private readonly string _sessionId; private readonly EventReceiverOrchestrator _eventReceiverOrchestrator; private readonly IContextProvider _contextProvider; - private readonly PropertyInjectionService _propertyInjectionService; - private readonly DataSourceInitializer _dataSourceInitializer; + private readonly ObjectLifecycleService _objectLifecycleService; private readonly Discovery.IHookDiscoveryService _hookDiscoveryService; private readonly TestArgumentRegistrationService _testArgumentRegistrationService; private readonly IMetadataFilterMatcher _filterMatcher; @@ -31,8 +30,7 @@ public TestBuilder( string sessionId, EventReceiverOrchestrator eventReceiverOrchestrator, IContextProvider contextProvider, - PropertyInjectionService propertyInjectionService, - DataSourceInitializer dataSourceInitializer, + ObjectLifecycleService objectLifecycleService, Discovery.IHookDiscoveryService hookDiscoveryService, TestArgumentRegistrationService testArgumentRegistrationService, IMetadataFilterMatcher filterMatcher) @@ -41,16 +39,17 @@ public TestBuilder( _hookDiscoveryService = hookDiscoveryService; _eventReceiverOrchestrator = eventReceiverOrchestrator; _contextProvider = contextProvider; - _propertyInjectionService = propertyInjectionService; - _dataSourceInitializer = dataSourceInitializer; + _objectLifecycleService = objectLifecycleService; _testArgumentRegistrationService = testArgumentRegistrationService; _filterMatcher = filterMatcher ?? throw new ArgumentNullException(nameof(filterMatcher)); } /// - /// Initializes any IAsyncInitializer objects in class data that were deferred during registration. + /// Initializes class data objects during test building. + /// Only IAsyncDiscoveryInitializer objects are initialized during discovery. + /// Regular IAsyncInitializer objects are deferred to execution phase. /// - private async Task InitializeDeferredClassDataAsync(object?[] classData) + private static async Task InitializeClassDataAsync(object?[] classData) { if (classData == null || classData.Length == 0) { @@ -59,20 +58,16 @@ private async Task InitializeDeferredClassDataAsync(object?[] classData) foreach (var data in classData) { - if (data is IAsyncInitializer asyncInitializer && data is not IDataSourceAttribute) - { - if (!ObjectInitializer.IsInitialized(data)) - { - await ObjectInitializer.InitializeAsync(data); - } - } + // Discovery: only IAsyncDiscoveryInitializer objects are initialized. + // Regular IAsyncInitializer objects are deferred to execution phase. + await ObjectInitializer.InitializeForDiscoveryAsync(data); } } private async Task CreateInstance(TestMetadata metadata, Type[] resolvedClassGenericArgs, object?[] classData, TestBuilderContext builderContext) { // Initialize any deferred IAsyncInitializer objects in class data - await InitializeDeferredClassDataAsync(classData); + await InitializeClassDataAsync(classData); // First try to create instance with ClassConstructor attribute // Use attributes from context if available @@ -136,6 +131,10 @@ public async Task> BuildTestsFromMetadataAsy try { + // Create a context for capturing output during test building + using var buildContext = new TestBuildContext(); + TestBuildContext.Current = buildContext; + // Handle GenericTestMetadata with ConcreteInstantiations if (metadata is GenericTestMetadata { ConcreteInstantiations.Count: > 0 } genericMetadata) { @@ -202,11 +201,17 @@ public async Task> BuildTestsFromMetadataAsy hasAnyClassData = true; classDataLoopIndex++; - var classData = DataUnwrapper.Unwrap(await classDataFactory() ?? []); + var classDataResult = await classDataFactory() ?? []; + var classData = DataUnwrapper.Unwrap(classDataResult); + + // Initialize objects before method data sources are evaluated. + // ObjectInitializer is phase-aware and will only initialize IAsyncDiscoveryInitializer during Discovery. + await InitializeClassDataAsync(classData); var needsInstanceForMethodDataSources = metadata.DataSources.Any(ds => ds is IAccessesInstanceData); object? instanceForMethodDataSources = null; + var discoveryInstanceUsed = false; if (needsInstanceForMethodDataSources) { @@ -245,6 +250,27 @@ public async Task> BuildTestsFromMetadataAsy // Non-generic class instanceForMethodDataSources = metadata.InstanceFactory([], classData); } + + // Initialize property data sources on the early instance so that + // method data sources can access fully-initialized properties. + // This is critical for scenarios like: + // [ClassDataSource>] public required ErrFixture Fixture { get; init; } + // public IEnumerable> TestExecutions => [() => Fixture.Value]; + // [MethodDataSource("TestExecutions")] [Test] public void MyTest(T value) { } + if (instanceForMethodDataSources != null) + { + var tempObjectBag = new ConcurrentDictionary(); + var tempEvents = new TestContextEvents(); + + await _objectLifecycleService.RegisterObjectAsync( + instanceForMethodDataSources, + tempObjectBag, + metadata.MethodMetadata, + tempEvents); + + // Discovery: only IAsyncDiscoveryInitializer is initialized + await ObjectInitializer.InitializeForDiscoveryAsync(instanceForMethodDataSources); + } } catch (Exception ex) { @@ -284,12 +310,17 @@ public async Task> BuildTestsFromMetadataAsy TestMetadata = metadata.MethodMetadata, Events = new TestContextEvents(), StateBag = new ConcurrentDictionary(), - DataSourceAttribute = methodDataSource + DataSourceAttribute = methodDataSource, + InitializedAttributes = testBuilderContext.InitializedAttributes, // Preserve attributes from parent context + ClassConstructor = testBuilderContext.ClassConstructor // Preserve ClassConstructor for instance creation }; classData = DataUnwrapper.Unwrap(await classDataFactory() ?? []); var methodData = DataUnwrapper.UnwrapWithTypes(await methodDataFactory() ?? [], metadata.MethodMetadata.Parameters); + // Initialize method data objects (ObjectInitializer is phase-aware) + await InitializeClassDataAsync(methodData); + // For concrete generic instantiations, check if the data is compatible with the expected types if (metadata.GenericMethodTypeArguments is { Length: > 0 }) { @@ -360,10 +391,21 @@ public async Task> BuildTestsFromMetadataAsy var basicSkipReason = GetBasicSkipReason(metadata, attributes); Func> instanceFactory; + bool isReusingDiscoveryInstance = false; + if (basicSkipReason is { Length: > 0 }) { instanceFactory = () => Task.FromResult(SkippedTestInstance.Instance); } + else if (methodDataLoopIndex == 1 && i == 0 && instanceForMethodDataSources != null && !discoveryInstanceUsed) + { + // Reuse the discovery instance for the first test to avoid duplicate initialization + var capturedInstance = instanceForMethodDataSources; + discoveryInstanceUsed = true; + isReusingDiscoveryInstance = true; + + instanceFactory = () => Task.FromResult(capturedInstance); + } else { var capturedMetadata = metadata; @@ -398,7 +440,7 @@ public async Task> BuildTestsFromMetadataAsy InitializedAttributes = attributes }; - var test = await BuildTestAsync(metadata, testData, testSpecificContext); + var test = await BuildTestAsync(metadata, testData, testSpecificContext, isReusingDiscoveryInstance); // If we have a basic skip reason, set it immediately if (!string.IsNullOrEmpty(basicSkipReason)) @@ -507,6 +549,18 @@ public async Task> BuildTestsFromMetadataAsy tests.Add(test); } } + + // Transfer captured build-time output to all test contexts + var capturedOutput = buildContext.GetCapturedOutput(); + var capturedErrorOutput = buildContext.GetCapturedErrorOutput(); + + if (!string.IsNullOrEmpty(capturedOutput) || !string.IsNullOrEmpty(capturedErrorOutput)) + { + foreach (var test in tests) + { + test.Context.SetBuildTimeOutput(capturedOutput, capturedErrorOutput); + } + } } catch (Exception ex) { @@ -732,10 +786,10 @@ private async Task GetDataSourcesAsync(IDataSourceAttrib return [NoDataSource.Instance]; } - // Initialize all data sources to ensure properties are injected + // Inject properties into data sources during discovery (IAsyncInitializer deferred to execution) foreach (var dataSource in dataSources) { - await _dataSourceInitializer.EnsureInitializedAsync(dataSource); + await _objectLifecycleService.InjectPropertiesAsync(dataSource); } return dataSources; @@ -749,16 +803,15 @@ private async Task GetDataSourcesAsync(IDataSourceAttrib IDataSourceAttribute dataSource, DataGeneratorMetadata dataGeneratorMetadata) { - // Ensure the data source is fully initialized before getting data rows - // This includes property injection and IAsyncInitializer.InitializeAsync - var initializedDataSource = await _dataSourceInitializer.EnsureInitializedAsync( + // Inject properties into data source during discovery (IAsyncInitializer deferred to execution) + var propertyInjectedDataSource = await _objectLifecycleService.InjectPropertiesAsync( dataSource, dataGeneratorMetadata.TestBuilderContext.Current.StateBag, dataGeneratorMetadata.TestInformation, dataGeneratorMetadata.TestBuilderContext.Current.Events); - // Now get data rows from the initialized data source - await foreach (var dataRow in initializedDataSource.GetDataRowsAsync(dataGeneratorMetadata)) + // Now get data rows from the property-injected data source + await foreach (var dataRow in propertyInjectedDataSource.GetDataRowsAsync(dataGeneratorMetadata)) { yield return dataRow; } @@ -766,7 +819,7 @@ private async Task GetDataSourcesAsync(IDataSourceAttrib [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Hook discovery service handles mode-specific logic; reflection calls suppressed in AOT mode")] [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Hook discovery service handles mode-specific logic; dynamic code suppressed in AOT mode")] - public async Task BuildTestAsync(TestMetadata metadata, TestData testData, TestBuilderContext testBuilderContext) + public async Task BuildTestAsync(TestMetadata metadata, TestData testData, TestBuilderContext testBuilderContext, bool isReusingDiscoveryInstance = false) { // Discover instance hooks for closed generic types (no-op in source gen mode) if (metadata.TestClassType is { IsGenericType: true, IsGenericTypeDefinition: false }) @@ -778,6 +831,9 @@ public async Task BuildTestAsync(TestMetadata metadata, var context = await CreateTestContextAsync(testId, metadata, testData, testBuilderContext); + // Mark if this test is reusing the discovery instance (already initialized) + context.IsDiscoveryInstanceReused = isReusingDiscoveryInstance; + context.Metadata.TestDetails.ClassInstance = PlaceholderInstance.Instance; // Arguments will be tracked by TestArgumentTrackingService during TestRegistered event @@ -915,7 +971,7 @@ private async Task InvokeTestRegisteredEventReceiversAsync(TestContext context) context.InternalDiscoveredTest = discoveredTest; // First, invoke the global test argument registration service to register shared instances - await _testArgumentRegistrationService.OnTestRegistered(registeredContext); + await _testArgumentRegistrationService.RegisterTestArgumentsAsync(context); var eventObjects = context.GetEligibleEventObjects(); @@ -983,14 +1039,13 @@ private async Task CreateFailedTestDetails(TestMetadata metadata, s private async Task InitializeAttributesAsync(Attribute[] attributes) { - // Initialize any attributes that need property injection or implement IAsyncInitializer - // This ensures they're fully initialized before being used + // Inject properties into data source attributes during discovery + // IAsyncInitializer.InitializeAsync is deferred to execution time foreach (var attribute in attributes) { if (attribute is IDataSourceAttribute dataSource) { - // Data source attributes need to be initialized with property injection - await _dataSourceInitializer.EnsureInitializedAsync(dataSource); + await _objectLifecycleService.InjectPropertiesAsync(dataSource); } } @@ -1339,6 +1394,9 @@ public async IAsyncEnumerable BuildTestsStreamingAsync( var classData = DataUnwrapper.Unwrap(await classDataFactory() ?? []); + // Initialize objects before method data sources are evaluated (ObjectInitializer is phase-aware) + await InitializeClassDataAsync(classData); + // Handle instance creation for method data sources var needsInstanceForMethodDataSources = metadata.DataSources.Any(ds => ds is IAccessesInstanceData); object? instanceForMethodDataSources = null; @@ -1352,6 +1410,20 @@ public async IAsyncEnumerable BuildTestsStreamingAsync( { continue; // Skip if instance creation failed } + + // Initialize property data sources on the early instance so that + // method data sources can access fully-initialized properties. + var tempObjectBag = new ConcurrentDictionary(); + var tempEvents = new TestContextEvents(); + + await _objectLifecycleService.RegisterObjectAsync( + instanceForMethodDataSources, + tempObjectBag, + metadata.MethodMetadata, + tempEvents); + + // Discovery: only IAsyncDiscoveryInitializer is initialized + await ObjectInitializer.InitializeForDiscoveryAsync(instanceForMethodDataSources); } // Stream through method data sources @@ -1462,6 +1534,9 @@ public async IAsyncEnumerable BuildTestsStreamingAsync( var methodData = DataUnwrapper.UnwrapWithTypes(await methodDataFactory() ?? [], metadata.MethodMetadata.Parameters); + // Initialize method data objects (ObjectInitializer is phase-aware) + await InitializeClassDataAsync(methodData); + // Check data compatibility for generic methods if (metadata.GenericMethodTypeArguments is { Length: > 0 }) { diff --git a/TUnit.Engine/CommandLineProviders/JUnitReporterCommandProvider.cs b/TUnit.Engine/CommandLineProviders/JUnitReporterCommandProvider.cs new file mode 100644 index 0000000000..39977ee5d6 --- /dev/null +++ b/TUnit.Engine/CommandLineProviders/JUnitReporterCommandProvider.cs @@ -0,0 +1,45 @@ +using Microsoft.Testing.Platform.CommandLine; +using Microsoft.Testing.Platform.Extensions; +using Microsoft.Testing.Platform.Extensions.CommandLine; + +namespace TUnit.Engine.CommandLineProviders; + +internal class JUnitReporterCommandProvider(IExtension extension) : ICommandLineOptionsProvider +{ + public const string JUnitOutputPathOption = "junit-output-path"; + + public Task IsEnabledAsync() => extension.IsEnabledAsync(); + + public string Uid => extension.Uid; + + public string Version => extension.Version; + + public string DisplayName => extension.DisplayName; + + public string Description => extension.Description; + + public IReadOnlyCollection GetCommandLineOptions() + { + return + [ + new CommandLineOption( + JUnitOutputPathOption, + "Path to output JUnit XML file (default: TestResults/{AssemblyName}-junit.xml)", + ArgumentArity.ExactlyOne, + false) + ]; + } + + public Task ValidateOptionArgumentsAsync( + CommandLineOption commandOption, + string[] arguments) + { + return ValidationResult.ValidTask; + } + + public Task ValidateCommandLineOptionsAsync( + ICommandLineOptions commandLineOptions) + { + return ValidationResult.ValidTask; + } +} diff --git a/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs b/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs index e0e541f163..86b942be33 100644 --- a/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs +++ b/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs @@ -1603,7 +1603,8 @@ private static bool IsCovariantCompatible(Type paramType, [DynamicallyAccessedMe { return valueTask.AsTask(); } - return Task.CompletedTask; + // F# Async support (reuses existing AsyncConvert logic) + return AsyncConvert.ConvertObject(result).AsTask(); } catch (TargetInvocationException tie) { diff --git a/TUnit.Engine/Extensions/TestApplicationBuilderExtensions.cs b/TUnit.Engine/Extensions/TestApplicationBuilderExtensions.cs index 83b7f0c98e..435894225f 100644 --- a/TUnit.Engine/Extensions/TestApplicationBuilderExtensions.cs +++ b/TUnit.Engine/Extensions/TestApplicationBuilderExtensions.cs @@ -21,6 +21,9 @@ public static void AddTUnit(this ITestApplicationBuilder testApplicationBuilder) var githubReporter = new GitHubReporter(extension); var githubReporterCommandProvider = new GitHubReporterCommandProvider(extension); + var junitReporter = new JUnitReporter(extension); + var junitReporterCommandProvider = new JUnitReporterCommandProvider(extension); + testApplicationBuilder.RegisterTestFramework( serviceProvider => new TestFrameworkCapabilities(CreateCapabilities(serviceProvider)), (capabilities, serviceProvider) => new TUnitTestFramework(extension, serviceProvider, capabilities)); @@ -46,6 +49,9 @@ public static void AddTUnit(this ITestApplicationBuilder testApplicationBuilder) // GitHub reporter configuration testApplicationBuilder.CommandLine.AddProvider(() => githubReporterCommandProvider); + // JUnit reporter configuration + testApplicationBuilder.CommandLine.AddProvider(() => junitReporterCommandProvider); + testApplicationBuilder.TestHost.AddDataConsumer(serviceProvider => { // Apply command-line configuration if provided @@ -58,6 +64,18 @@ public static void AddTUnit(this ITestApplicationBuilder testApplicationBuilder) return githubReporter; }); testApplicationBuilder.TestHost.AddTestHostApplicationLifetime(_ => githubReporter); + + testApplicationBuilder.TestHost.AddDataConsumer(serviceProvider => + { + // Apply command-line configuration if provided + var commandLineOptions = serviceProvider.GetRequiredService(); + if (commandLineOptions.TryGetOptionArgumentList(JUnitReporterCommandProvider.JUnitOutputPathOption, out var pathArgs)) + { + junitReporter.SetOutputPath(pathArgs[0]); + } + return junitReporter; + }); + testApplicationBuilder.TestHost.AddTestHostApplicationLifetime(_ => junitReporter); } private static IReadOnlyCollection CreateCapabilities(IServiceProvider serviceProvider) diff --git a/TUnit.Engine/Framework/TUnitServiceProvider.cs b/TUnit.Engine/Framework/TUnitServiceProvider.cs index 823d643784..9a31e5eb99 100644 --- a/TUnit.Engine/Framework/TUnitServiceProvider.cs +++ b/TUnit.Engine/Framework/TUnitServiceProvider.cs @@ -51,9 +51,7 @@ public ITestExecutionFilter? Filter public TUnitInitializer Initializer { get; } public CancellationTokenSource FailFastCancellationSource { get; } public ParallelLimitLockProvider ParallelLimitLockProvider { get; } - public PropertyInjectionService PropertyInjectionService { get; } - public DataSourceInitializer DataSourceInitializer { get; } - public ObjectRegistrationService ObjectRegistrationService { get; } + public ObjectLifecycleService ObjectLifecycleService { get; } public bool AfterSessionHooksFailed { get; set; } [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Reflection mode is not used in AOT/trimmed scenarios")] @@ -104,25 +102,29 @@ public TUnitServiceProvider(IExtension extension, loggerFactory.CreateLogger(), logLevelProvider)); - // Create initialization services - // Note: Circular dependency managed through two-phase initialization - // Phase 1: Create services with partial dependencies - DataSourceInitializer = Register(new DataSourceInitializer()); - PropertyInjectionService = Register(new PropertyInjectionService(DataSourceInitializer)); - ObjectRegistrationService = Register(new ObjectRegistrationService(PropertyInjectionService)); - - // Phase 2: Complete dependencies (Initialize methods accept IObjectRegistry to break circular dependency) - PropertyInjectionService.Initialize(ObjectRegistrationService); - DataSourceInitializer.Initialize(PropertyInjectionService); + // Create initialization services using Lazy to break circular dependencies + // No more two-phase initialization with Initialize() calls + var objectGraphDiscoveryService = Register(new ObjectGraphDiscoveryService()); + // Keep TrackableObjectGraphProvider for ObjectTracker (TUnit.Core dependency) var trackableObjectGraphProvider = new TrackableObjectGraphProvider(); var disposer = new Disposer(Logger); var objectTracker = new ObjectTracker(trackableObjectGraphProvider, disposer); + // Use Lazy to break circular dependency between PropertyInjector and ObjectLifecycleService + // PropertyInjector now depends on IInitializationCallback interface (implemented by ObjectLifecycleService) + // This follows Dependency Inversion Principle and improves testability + ObjectLifecycleService? objectLifecycleServiceInstance = null; + var lazyInitializationCallback = new Lazy(() => objectLifecycleServiceInstance!); + var lazyPropertyInjector = new Lazy(() => new PropertyInjector(lazyInitializationCallback, TestSessionId)); + + objectLifecycleServiceInstance = new ObjectLifecycleService(lazyPropertyInjector, objectGraphDiscoveryService, objectTracker); + ObjectLifecycleService = Register(objectLifecycleServiceInstance); + // Register the test argument registration service to handle object registration for shared instances - var testArgumentRegistrationService = Register(new TestArgumentRegistrationService(ObjectRegistrationService, objectTracker)); + var testArgumentRegistrationService = Register(new TestArgumentRegistrationService(ObjectLifecycleService)); TestFilterService = Register(new TestFilterService(Logger, testArgumentRegistrationService)); @@ -135,7 +137,7 @@ public TUnitServiceProvider(IExtension extension, CancellationToken = Register(new EngineCancellationToken()); - EventReceiverOrchestrator = Register(new EventReceiverOrchestrator(Logger, trackableObjectGraphProvider)); + EventReceiverOrchestrator = Register(new EventReceiverOrchestrator(Logger)); HookCollectionService = Register(new HookCollectionService(EventReceiverOrchestrator)); ParallelLimitLockProvider = Register(new ParallelLimitLockProvider()); @@ -145,8 +147,9 @@ public TUnitServiceProvider(IExtension extension, var hookExecutor = Register(new HookExecutor(HookCollectionService, ContextProvider, EventReceiverOrchestrator)); var lifecycleCoordinator = Register(new TestLifecycleCoordinator()); var beforeHookTaskCache = Register(new BeforeHookTaskCache()); + var afterHookPairTracker = Register(new AfterHookPairTracker()); - TestExecutor = Register(new TestExecutor(hookExecutor, lifecycleCoordinator, beforeHookTaskCache, ContextProvider, EventReceiverOrchestrator)); + TestExecutor = Register(new TestExecutor(hookExecutor, lifecycleCoordinator, beforeHookTaskCache, afterHookPairTracker, ContextProvider, EventReceiverOrchestrator)); var testExecutionGuard = Register(new TestExecutionGuard()); var testStateManager = Register(new TestStateManager()); @@ -173,7 +176,7 @@ public TUnitServiceProvider(IExtension extension, var dependencyExpander = Register(new MetadataDependencyExpander(filterMatcher)); var testBuilder = Register( - new TestBuilder(TestSessionId, EventReceiverOrchestrator, ContextProvider, PropertyInjectionService, DataSourceInitializer, hookDiscoveryService, testArgumentRegistrationService, filterMatcher)); + new TestBuilder(TestSessionId, EventReceiverOrchestrator, ContextProvider, ObjectLifecycleService, hookDiscoveryService, testArgumentRegistrationService, filterMatcher)); TestBuilderPipeline = Register( new TestBuilderPipeline( @@ -187,7 +190,7 @@ public TUnitServiceProvider(IExtension extension, // Create test finder service after discovery service so it can use its cache TestFinder = Register(new TestFinder(DiscoveryService)); - var testInitializer = new TestInitializer(EventReceiverOrchestrator, PropertyInjectionService, objectTracker); + var testInitializer = new TestInitializer(EventReceiverOrchestrator, ObjectLifecycleService); // Create the new TestCoordinator that orchestrates the granular services var testCoordinator = Register( diff --git a/TUnit.Engine/Helpers/EnvironmentVariableCache.cs b/TUnit.Engine/Helpers/EnvironmentVariableCache.cs deleted file mode 100644 index ce0a3ddf70..0000000000 --- a/TUnit.Engine/Helpers/EnvironmentVariableCache.cs +++ /dev/null @@ -1,192 +0,0 @@ -using System.Collections.Concurrent; - -namespace TUnit.Engine.Helpers; - -/// -/// Centralized cache for environment variables to avoid repeated system calls -/// Initializes all environment variables on first access and caches them for the lifetime of the application -/// -internal static class EnvironmentVariableCache -{ - private static readonly ConcurrentDictionary _cache = new(); - private static readonly object _initLock = new(); - private static bool _initialized = false; - - /// - /// All environment variable keys that TUnit cares about - /// This helps us cache only the variables we need rather than all environment variables - /// - private static readonly string[] _tunitEnvironmentVariables = - [ - // TUnit specific variables - "TUNIT_DISCOVERY_DIAGNOSTICS", - "TUNIT_DISCOVERY_TIMEOUT_SECONDS", - "TUNIT_DATA_SOURCE_TIMEOUT_SECONDS", - "TUNIT_EXECUTION_MODE", - "TUNIT_ADAPTIVE_MIN_PARALLELISM", - "TUNIT_ADAPTIVE_MAX_PARALLELISM", - "TUNIT_ADAPTIVE_METRICS", - "TUNIT_DISABLE_GITHUB_REPORTER", - "TUNIT_GITHUB_REPORTER_STYLE", - - // CI environment detection variables - "CI", - "CONTINUOUS_INTEGRATION", - "BUILD_ID", - "BUILD_NUMBER", - "GITHUB_ACTIONS", - "GITLAB_CI", - "AZURE_PIPELINES", - "JENKINS_URL", - "TEAMCITY_VERSION", - "APPVEYOR", - "CIRCLECI", - "TRAVIS", - - // Container detection variables - "DOTNET_RUNNING_IN_CONTAINER", - "CONTAINER", - "KUBERNETES_SERVICE_HOST", - - // GitHub specific variables - "DISABLE_GITHUB_REPORTER", - "GITHUB_STEP_SUMMARY" - ]; - - /// - /// Gets the cached value of an environment variable - /// Initializes the cache on first call - /// - /// The name of the environment variable - /// The environment variable value or null if not set - public static string? Get(string variableName) - { - EnsureInitialized(); - _cache.TryGetValue(variableName, out var value); - return value; - } - - /// - /// Gets multiple cached environment variable values - /// Useful for checking multiple CI or container detection variables - /// - /// The names of the environment variables to retrieve - /// An array of environment variable values (nulls for unset variables) - public static string?[] GetMultiple(params string[] variableNames) - { - EnsureInitialized(); - var result = new string?[variableNames.Length]; - for (var i = 0; i < variableNames.Length; i++) - { - _cache.TryGetValue(variableNames[i], out var value); - result[i] = value; - } - return result; - } - - /// - /// Checks if any of the specified environment variables have non-empty values - /// Useful for CI and container detection - /// - /// The environment variable names to check - /// True if any of the variables have non-empty values - public static bool HasAnyNonEmpty(params string[] variableNames) - { - EnsureInitialized(); - for (var i = 0; i < variableNames.Length; i++) - { - _cache.TryGetValue(variableNames[i], out var value); - if (!string.IsNullOrEmpty(value)) - { - return true; - } - } - return false; - } - - /// - /// CI environment variables for quick access - /// - public static readonly string[] CiVariables = - [ - "CI", - "CONTINUOUS_INTEGRATION", - "BUILD_ID", - "BUILD_NUMBER", - "GITHUB_ACTIONS", - "GITLAB_CI", - "AZURE_PIPELINES", - "JENKINS_URL", - "TEAMCITY_VERSION", - "APPVEYOR", - "CIRCLECI", - "TRAVIS" - ]; - - /// - /// Container environment variables for quick access - /// - public static readonly string[] ContainerVariables = - [ - "DOTNET_RUNNING_IN_CONTAINER", - "CONTAINER", - "KUBERNETES_SERVICE_HOST" - ]; - - /// - /// Convenience method to check if running in a CI environment - /// - public static bool IsRunningInCI() - { - return HasAnyNonEmpty(CiVariables); - } - - /// - /// Convenience method to check if running in a container - /// - public static bool IsRunningInContainer() - { - return HasAnyNonEmpty(ContainerVariables); - } - - /// - /// Initializes the cache with all TUnit environment variables - /// Thread-safe and only runs once - /// - private static void EnsureInitialized() - { - if (_initialized) - { - return; - } - - lock (_initLock) - { - if (_initialized) - { - return; - } - - // Cache all TUnit-related environment variables - foreach (var variableName in _tunitEnvironmentVariables) - { - var value = Environment.GetEnvironmentVariable(variableName); - _cache.TryAdd(variableName, value); - } - - _initialized = true; - } - } - - /// - /// For testing purposes - allows clearing and reinitializing the cache - /// - internal static void ClearCache() - { - lock (_initLock) - { - _cache.Clear(); - _initialized = false; - } - } -} \ No newline at end of file diff --git a/TUnit.Engine/Helpers/ExecutionModeHelper.cs b/TUnit.Engine/Helpers/ExecutionModeHelper.cs index bc654ca3cf..6364fceffd 100644 --- a/TUnit.Engine/Helpers/ExecutionModeHelper.cs +++ b/TUnit.Engine/Helpers/ExecutionModeHelper.cs @@ -67,7 +67,7 @@ public static bool IsSourceGenerationMode(ICommandLineOptions commandLineOptions } // Check environment variable - var envMode = EnvironmentVariableCache.Get("TUNIT_EXECUTION_MODE"); + var envMode = Environment.GetEnvironmentVariable("TUNIT_EXECUTION_MODE"); if (!string.IsNullOrEmpty(envMode)) { var mode = envMode!.ToLowerInvariant(); diff --git a/TUnit.Engine/Reporters/GitHubReporter.cs b/TUnit.Engine/Reporters/GitHubReporter.cs index 69deaedac5..f7c37ef5c0 100644 --- a/TUnit.Engine/Reporters/GitHubReporter.cs +++ b/TUnit.Engine/Reporters/GitHubReporter.cs @@ -7,7 +7,6 @@ using Microsoft.Testing.Platform.Extensions.Messages; using Microsoft.Testing.Platform.Extensions.TestHost; using TUnit.Engine.Framework; -using TUnit.Engine.Helpers; namespace TUnit.Engine.Reporters; @@ -25,18 +24,18 @@ public class GitHubReporter(IExtension extension) : IDataConsumer, ITestHostAppl public async Task IsEnabledAsync() { - if (EnvironmentVariableCache.Get("TUNIT_DISABLE_GITHUB_REPORTER") is not null || - EnvironmentVariableCache.Get("DISABLE_GITHUB_REPORTER") is not null) + if (Environment.GetEnvironmentVariable("TUNIT_DISABLE_GITHUB_REPORTER") is not null || + Environment.GetEnvironmentVariable("DISABLE_GITHUB_REPORTER") is not null) { return false; } - if (EnvironmentVariableCache.Get("GITHUB_ACTIONS") is null) + if (Environment.GetEnvironmentVariable("GITHUB_ACTIONS") is null) { return false; } - if (EnvironmentVariableCache.Get("GITHUB_STEP_SUMMARY") is not { } fileName + if (Environment.GetEnvironmentVariable("GITHUB_STEP_SUMMARY") is not { } fileName || !File.Exists(fileName)) { return false; @@ -45,7 +44,7 @@ public async Task IsEnabledAsync() _outputSummaryFilePath = fileName; // Determine reporter style from environment variable or default to collapsible - var styleEnv = EnvironmentVariableCache.Get("TUNIT_GITHUB_REPORTER_STYLE"); + var styleEnv = Environment.GetEnvironmentVariable("TUNIT_GITHUB_REPORTER_STYLE"); if (!string.IsNullOrEmpty(styleEnv)) { _reporterStyle = styleEnv!.ToLowerInvariant() switch @@ -173,8 +172,9 @@ public Task AfterRunAsync(int exitCode, CancellationToken cancellation) detailsBuilder.AppendLine(); detailsBuilder.AppendLine("### Details"); detailsBuilder.AppendLine(); - detailsBuilder.AppendLine("| Test | Status | Details | Duration |"); - detailsBuilder.AppendLine("| --- | --- | --- | --- |"); + detailsBuilder.AppendLine(""""""); + detailsBuilder.AppendLine(""); + detailsBuilder.AppendLine(""); foreach (var testNodeUpdateMessage in last.Values) { @@ -191,14 +191,20 @@ public Task AfterRunAsync(int exitCode, CancellationToken cancellation) var status = GetStatus(stateProperty); - var details = GetDetails(stateProperty, testNodeUpdateMessage.TestNode.Properties).Replace("\n", "
"); + var details = GetDetails(stateProperty, testNodeUpdateMessage.TestNode.Properties); var timingProperty = testNodeUpdateMessage.TestNode.Properties.AsEnumerable().OfType().FirstOrDefault(); var duration = timingProperty?.GlobalTiming.Duration; - detailsBuilder.AppendLine($"| {name} | {status} | {details} | {duration} |"); + detailsBuilder.AppendLine(""); + detailsBuilder.AppendLine($""); + detailsBuilder.AppendLine($""); + detailsBuilder.AppendLine($""); + detailsBuilder.AppendLine($""); + detailsBuilder.AppendLine(""); } + detailsBuilder.AppendLine("
TestStatusDetailsDuration
{name}{status}{details}{duration}
"); // Wrap in collapsible section if using collapsible style if (_reporterStyle == GitHubReporterStyle.Collapsible) diff --git a/TUnit.Engine/Reporters/JUnitReporter.cs b/TUnit.Engine/Reporters/JUnitReporter.cs new file mode 100644 index 0000000000..2a34661558 --- /dev/null +++ b/TUnit.Engine/Reporters/JUnitReporter.cs @@ -0,0 +1,160 @@ +using System.Collections.Concurrent; +using System.Reflection; +using System.Text; +using Microsoft.Testing.Platform.Extensions; +using Microsoft.Testing.Platform.Extensions.Messages; +using Microsoft.Testing.Platform.Extensions.TestHost; +using TUnit.Engine.Framework; +using TUnit.Engine.Xml; + +namespace TUnit.Engine.Reporters; + +public class JUnitReporter(IExtension extension) : IDataConsumer, ITestHostApplicationLifetime, IFilterReceiver +{ + private string _outputPath = null!; + private bool _isEnabled; + + public async Task IsEnabledAsync() + { + // Check if explicitly disabled + if (Environment.GetEnvironmentVariable("TUNIT_DISABLE_JUNIT_REPORTER") is not null) + { + return false; + } + + // Check if explicitly enabled OR running in GitLab CI + var explicitlyEnabled = Environment.GetEnvironmentVariable("TUNIT_ENABLE_JUNIT_REPORTER") is not null; + var runningInGitLab = Environment.GetEnvironmentVariable("GITLAB_CI") is not null || + Environment.GetEnvironmentVariable("CI_SERVER") is not null; + + if (!explicitlyEnabled && !runningInGitLab) + { + return false; + } + + // Determine output path + _outputPath = Environment.GetEnvironmentVariable("JUNIT_XML_OUTPUT_PATH") + ?? GetDefaultOutputPath(); + + _isEnabled = true; + return await extension.IsEnabledAsync(); + } + + public string Uid { get; } = $"{extension.Uid}JUnitReporter"; + + public string Version => extension.Version; + + public string DisplayName => extension.DisplayName; + + public string Description => extension.Description; + + private readonly ConcurrentDictionary> _updates = []; + + public Task ConsumeAsync(IDataProducer dataProducer, IData value, CancellationToken cancellationToken) + { + var testNodeUpdateMessage = (TestNodeUpdateMessage)value; + + _updates.GetOrAdd(testNodeUpdateMessage.TestNode.Uid.Value, []).Add(testNodeUpdateMessage); + + return Task.CompletedTask; + } + + public Type[] DataTypesConsumed { get; } = [typeof(TestNodeUpdateMessage)]; + + public Task BeforeRunAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public async Task AfterRunAsync(int exitCode, CancellationToken cancellation) + { + if (!_isEnabled || _updates.Count == 0) + { + return; + } + + // Get the last update for each test + var lastUpdates = new List(_updates.Count); + foreach (var kvp in _updates.Where(kvp => kvp.Value.Count > 0)) + { + lastUpdates.Add(kvp.Value[kvp.Value.Count - 1]); + } + + // Generate JUnit XML + var xmlContent = JUnitXmlWriter.GenerateXml(lastUpdates, Filter); + + if (string.IsNullOrEmpty(xmlContent)) + { + return; + } + + // Write to file with retry logic + await WriteXmlFileAsync(_outputPath, xmlContent, cancellation); + } + + public string? Filter { get; set; } + + internal void SetOutputPath(string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentException("Output path cannot be null or empty", nameof(path)); + } + + _outputPath = path; + } + + private static string GetDefaultOutputPath() + { + var assemblyName = Assembly.GetEntryAssembly()?.GetName().Name ?? "TestResults"; + return Path.Combine("TestResults", $"{assemblyName}-junit.xml"); + } + + private static async Task WriteXmlFileAsync(string path, string content, CancellationToken cancellationToken) + { + // Ensure directory exists + var directory = Path.GetDirectoryName(path); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + const int maxAttempts = 5; + + for (int attempt = 1; attempt <= maxAttempts; attempt++) + { + try + { +#if NET + await File.WriteAllTextAsync(path, content, Encoding.UTF8, cancellationToken); +#else + File.WriteAllText(path, content, Encoding.UTF8); +#endif + Console.WriteLine($"JUnit XML report written to: {path}"); + return; + } + catch (IOException ex) when (attempt < maxAttempts && IsFileLocked(ex)) + { + var baseDelay = 50 * Math.Pow(2, attempt - 1); + var jitter = Random.Shared.Next(0, 50); + var delay = (int)(baseDelay + jitter); + + Console.WriteLine($"JUnit XML file is locked, retrying in {delay}ms (attempt {attempt}/{maxAttempts})"); + await Task.Delay(delay, cancellationToken); + } + } + + Console.WriteLine($"Failed to write JUnit XML report to: {path} after {maxAttempts} attempts"); + } + + private static bool IsFileLocked(IOException exception) + { + // Check if the exception is due to the file being locked/in use + // HResult 0x80070020 is ERROR_SHARING_VIOLATION on Windows + // HResult 0x80070021 is ERROR_LOCK_VIOLATION on Windows + var errorCode = exception.HResult & 0xFFFF; + return errorCode == 0x20 || errorCode == 0x21 || + exception.Message.Contains("being used by another process") || + exception.Message.Contains("access denied", StringComparison.OrdinalIgnoreCase); + } +} diff --git a/TUnit.Engine/Services/AfterHookPairTracker.cs b/TUnit.Engine/Services/AfterHookPairTracker.cs new file mode 100644 index 0000000000..5e6e12deb5 --- /dev/null +++ b/TUnit.Engine/Services/AfterHookPairTracker.cs @@ -0,0 +1,155 @@ +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using TUnit.Core.Data; + +namespace TUnit.Engine.Services; + +/// +/// Responsible for ensuring After hooks run even when tests are cancelled. +/// When a Before hook completes, this tracker registers the corresponding After hook +/// to run on cancellation, guaranteeing cleanup even if the test is aborted. +/// Follows Single Responsibility Principle - only handles After hook pairing and cancellation registration. +/// +internal sealed class AfterHookPairTracker +{ + // Cached After hook tasks to ensure they run only once (prevent double execution) + private readonly ThreadSafeDictionary>> _afterClassTasks = new(); + private readonly ThreadSafeDictionary>> _afterAssemblyTasks = new(); + private Task>? _afterTestSessionTask; + private readonly object _testSessionLock = new(); + private readonly object _classLock = new(); + + // Track cancellation registrations for cleanup + private readonly ConcurrentBag _registrations = []; + + /// + /// Registers Session After hooks to run on cancellation or normal completion. + /// Ensures After hooks run exactly once even if called both ways. + /// + public void RegisterAfterTestSessionHook( + CancellationToken cancellationToken, + Func>> afterHookExecutor) + { + // Register callback to run After hook on cancellation + var registration = cancellationToken.Register(() => + { + // Use sync-over-async here because CancellationToken.Register requires Action (not Func) + // Fire-and-forget is acceptable here - exceptions will be collected when hooks run normally + _ = GetOrCreateAfterTestSessionTask(afterHookExecutor); + }); + + _registrations.Add(registration); + } + + /// + /// Registers Assembly After hooks to run on cancellation or normal completion. + /// Ensures After hooks run exactly once even if called both ways. + /// + public void RegisterAfterAssemblyHook( + Assembly assembly, + CancellationToken cancellationToken, + Func>> afterHookExecutor) + { + var registration = cancellationToken.Register(() => + { + _ = GetOrCreateAfterAssemblyTask(assembly, afterHookExecutor); + }); + + _registrations.Add(registration); + } + + /// + /// Registers Class After hooks to run on cancellation or normal completion. + /// Ensures After hooks run exactly once even if called both ways. + /// + public void RegisterAfterClassHook( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] + Type testClass, + HookExecutor hookExecutor, + CancellationToken cancellationToken) + { + var registration = cancellationToken.Register(() => + { + _ = GetOrCreateAfterClassTask(testClass, hookExecutor, CancellationToken.None); + }); + + _registrations.Add(registration); + } + + /// + /// Gets or creates the After Test Session task, ensuring it runs only once. + /// Thread-safe using double-checked locking. + /// Returns the exceptions from hook execution. + /// + public ValueTask> GetOrCreateAfterTestSessionTask(Func>> taskFactory) + { + if (_afterTestSessionTask != null) + { + return new ValueTask>(_afterTestSessionTask); + } + + lock (_testSessionLock) + { + if (_afterTestSessionTask == null) + { + _afterTestSessionTask = taskFactory().AsTask(); + } + return new ValueTask>(_afterTestSessionTask); + } + } + + /// + /// Gets or creates the After Assembly task for the specified assembly. + /// Thread-safe using ThreadSafeDictionary. + /// Returns the exceptions from hook execution. + /// + public ValueTask> GetOrCreateAfterAssemblyTask(Assembly assembly, Func>> taskFactory) + { + var task = _afterAssemblyTasks.GetOrAdd(assembly, a => taskFactory(a).AsTask()); + return new ValueTask>(task); + } + + /// + /// Gets or creates the After Class task for the specified test class. + /// Thread-safe using double-checked locking. + /// Returns the exceptions from hook execution. + /// + public ValueTask> GetOrCreateAfterClassTask( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] + Type testClass, + HookExecutor hookExecutor, + CancellationToken cancellationToken) + { + if (_afterClassTasks.TryGetValue(testClass, out var existingTask)) + { + return new ValueTask>(existingTask); + } + + lock (_classLock) + { + if (_afterClassTasks.TryGetValue(testClass, out existingTask)) + { + return new ValueTask>(existingTask); + } + + // Call ExecuteAfterClassHooksAsync directly with the annotated testClass + // The factory ignores the key since we've already created the task with the annotated type + var newTask = hookExecutor.ExecuteAfterClassHooksAsync(testClass, cancellationToken).AsTask(); + _afterClassTasks.GetOrAdd(testClass, _ => newTask); + return new ValueTask>(newTask); + } + } + + /// + /// Disposes all cancellation token registrations. + /// Should be called at the end of test execution to clean up resources. + /// + public void Dispose() + { + foreach (var registration in _registrations) + { + registration.Dispose(); + } + } +} diff --git a/TUnit.Engine/Services/BeforeHookTaskCache.cs b/TUnit.Engine/Services/BeforeHookTaskCache.cs index 3b76326dac..1a23fe93af 100644 --- a/TUnit.Engine/Services/BeforeHookTaskCache.cs +++ b/TUnit.Engine/Services/BeforeHookTaskCache.cs @@ -1,4 +1,3 @@ -using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Reflection; using TUnit.Core.Data; @@ -16,8 +15,9 @@ internal sealed class BeforeHookTaskCache private readonly ThreadSafeDictionary _beforeAssemblyTasks = new(); private Task? _beforeTestSessionTask; private readonly object _testSessionLock = new(); + private readonly object _classLock = new(); - public ValueTask GetOrCreateBeforeTestSessionTask(Func taskFactory) + public ValueTask GetOrCreateBeforeTestSessionTask(Func taskFactory, CancellationToken cancellationToken) { if (_beforeTestSessionTask != null) { @@ -28,23 +28,41 @@ public ValueTask GetOrCreateBeforeTestSessionTask(Func taskFactory) { if (_beforeTestSessionTask == null) { - _beforeTestSessionTask = taskFactory().AsTask(); + _beforeTestSessionTask = taskFactory(cancellationToken).AsTask(); } return new ValueTask(_beforeTestSessionTask); } } - public ValueTask GetOrCreateBeforeAssemblyTask(Assembly assembly, Func taskFactory) + public ValueTask GetOrCreateBeforeAssemblyTask(Assembly assembly, Func taskFactory, CancellationToken cancellationToken) { - var task = _beforeAssemblyTasks.GetOrAdd(assembly, a => taskFactory(a).AsTask()); + var task = _beforeAssemblyTasks.GetOrAdd(assembly, a => taskFactory(a, cancellationToken).AsTask()); return new ValueTask(task); } public ValueTask GetOrCreateBeforeClassTask( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] - Type testClass, Func taskFactory) + Type testClass, + HookExecutor hookExecutor, + CancellationToken cancellationToken) { - var task = _beforeClassTasks.GetOrAdd(testClass, t => taskFactory(t).AsTask()); - return new ValueTask(task); + if (_beforeClassTasks.TryGetValue(testClass, out var existingTask)) + { + return new ValueTask(existingTask); + } + + lock (_classLock) + { + if (_beforeClassTasks.TryGetValue(testClass, out existingTask)) + { + return new ValueTask(existingTask); + } + + // Call ExecuteBeforeClassHooksAsync directly with the annotated testClass + // The factory ignores the key since we've already created the task with the annotated type + var newTask = hookExecutor.ExecuteBeforeClassHooksAsync(testClass, cancellationToken).AsTask(); + _beforeClassTasks.GetOrAdd(testClass, _ => newTask); + return new ValueTask(newTask); + } } } diff --git a/TUnit.Engine/Services/DataSourceInitializer.cs b/TUnit.Engine/Services/DataSourceInitializer.cs deleted file mode 100644 index ae69a2f52e..0000000000 --- a/TUnit.Engine/Services/DataSourceInitializer.cs +++ /dev/null @@ -1,241 +0,0 @@ -using System.Collections.Concurrent; -using TUnit.Core; -using TUnit.Core.Interfaces; -using TUnit.Core.PropertyInjection; - -namespace TUnit.Engine.Services; - -/// -/// Centralized service responsible for initializing data source instances. -/// Ensures all data sources are properly initialized before use, regardless of where they're used -/// (properties, constructor arguments, or method arguments). -/// -internal sealed class DataSourceInitializer -{ - private readonly ConcurrentDictionary> _initializationTasks = new(); - private PropertyInjectionService? _propertyInjectionService; - - /// - /// Completes initialization by providing the PropertyInjectionService. - /// This two-phase initialization breaks the circular dependency. - /// - public void Initialize(PropertyInjectionService propertyInjectionService) - { - _propertyInjectionService = propertyInjectionService ?? throw new ArgumentNullException(nameof(propertyInjectionService)); - } - - /// - /// Ensures a data source instance is fully initialized before use. - /// This includes property injection and calling IAsyncInitializer if implemented. - /// Optimized with fast-path for already-initialized data sources. - /// - public async ValueTask EnsureInitializedAsync( - T dataSource, - ConcurrentDictionary? objectBag = null, - MethodMetadata? methodMetadata = null, - TestContextEvents? events = null, - CancellationToken cancellationToken = default) where T : notnull - { - if (dataSource == null) - { - throw new ArgumentNullException(nameof(dataSource)); - } - - // Fast path: Check if already initialized (avoids Task allocation) - if (_initializationTasks.TryGetValue(dataSource, out var existingTcs) && existingTcs.Task.IsCompleted) - { - // Already initialized - return synchronously without allocating a Task - if (existingTcs.Task.IsFaulted) - { - // Re-throw the original exception - await existingTcs.Task.ConfigureAwait(false); - } - - return dataSource; - } - - // Slow path: Need to initialize or wait for initialization - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - existingTcs = _initializationTasks.GetOrAdd(dataSource, tcs); - - if (existingTcs == tcs) - { - try - { - await InitializeDataSourceAsync(dataSource, objectBag, methodMetadata, events, cancellationToken).ConfigureAwait(false); - tcs.SetResult(true); - } - catch (Exception ex) - { - tcs.SetException(ex); - throw; - } - } - else - { - // Another thread is initializing or already initialized - wait for it - await existingTcs.Task.ConfigureAwait(false); - - // Check cancellation after waiting - if (cancellationToken.CanBeCanceled) - { - cancellationToken.ThrowIfCancellationRequested(); - } - } - - return dataSource; - } - - /// - /// Initializes a data source instance with the complete lifecycle. - /// - private async Task InitializeDataSourceAsync( - object dataSource, - ConcurrentDictionary? objectBag, - MethodMetadata? methodMetadata, - TestContextEvents? events, - CancellationToken cancellationToken) - { - try - { - // Ensure we have required context - objectBag ??= new ConcurrentDictionary(); - events ??= new TestContextEvents(); - - // Initialize the data source directly here - // Step 1: Property injection (if PropertyInjectionService has been initialized) - if (_propertyInjectionService != null && PropertyInjectionCache.HasInjectableProperties(dataSource.GetType())) - { - await _propertyInjectionService.InjectPropertiesIntoObjectAsync( - dataSource, objectBag, methodMetadata, events); - } - - // Step 2: Initialize nested property-injected objects (deepest first) - // This ensures that when the parent's IAsyncInitializer runs, all nested objects are already initialized - await InitializeNestedObjectsAsync(dataSource, cancellationToken); - - // Step 3: IAsyncInitializer on the data source itself - if (dataSource is IAsyncInitializer asyncInitializer) - { - await ObjectInitializer.InitializeAsync(asyncInitializer, cancellationToken); - } - } - catch (Exception ex) - { - throw new InvalidOperationException( - $"Failed to initialize data source of type '{dataSource.GetType().Name}': {ex.Message}", ex); - } - } - - /// - /// Initializes all nested property-injected objects in depth-first order. - /// This ensures that when the parent's IAsyncInitializer runs, all nested dependencies are already initialized. - /// - private async Task InitializeNestedObjectsAsync(object rootObject, CancellationToken cancellationToken) - { - var objectsByDepth = new Dictionary>(capacity: 4); - var visitedObjects = new HashSet(); - - // Collect all nested property-injected objects grouped by depth - CollectNestedObjects(rootObject, objectsByDepth, visitedObjects, currentDepth: 1); - - // Initialize objects deepest-first (highest depth to lowest) - var depths = objectsByDepth.Keys.OrderByDescending(depth => depth); - - foreach (var depth in depths) - { - var objectsAtDepth = objectsByDepth[depth]; - - // Initialize all objects at this depth in parallel - await Task.WhenAll(objectsAtDepth.Select(obj => ObjectInitializer.InitializeAsync(obj, cancellationToken).AsTask())); - } - } - - /// - /// Recursively collects all nested property-injected objects grouped by depth. - /// - private void CollectNestedObjects( - object obj, - Dictionary> objectsByDepth, - HashSet visitedObjects, - int currentDepth) - { - var plan = PropertyInjectionCache.GetOrCreatePlan(obj.GetType()); - - if (!SourceRegistrar.IsEnabled) - { - // Reflection mode - foreach (var prop in plan.ReflectionProperties) - { - var value = prop.Property.GetValue(obj); - - if (value == null || !visitedObjects.Add(value)) - { - continue; - } - - // Add to the current depth level if it has injectable properties or implements IAsyncInitializer - if (PropertyInjectionCache.HasInjectableProperties(value.GetType()) || value is IAsyncInitializer) - { - if (!objectsByDepth.ContainsKey(currentDepth)) - { - objectsByDepth[currentDepth] = []; - } - - objectsByDepth[currentDepth].Add(value); - } - - // Recursively collect nested objects - if (PropertyInjectionCache.HasInjectableProperties(value.GetType())) - { - CollectNestedObjects(value, objectsByDepth, visitedObjects, currentDepth + 1); - } - } - } - else - { - // Source-generated mode - foreach (var metadata in plan.SourceGeneratedProperties) - { - var property = metadata.ContainingType.GetProperty(metadata.PropertyName); - - if (property == null || !property.CanRead) - { - continue; - } - - var value = property.GetValue(obj); - - if (value == null || !visitedObjects.Add(value)) - { - continue; - } - - // Add to the current depth level if it has injectable properties or implements IAsyncInitializer - if (PropertyInjectionCache.HasInjectableProperties(value.GetType()) || value is IAsyncInitializer) - { - if (!objectsByDepth.ContainsKey(currentDepth)) - { - objectsByDepth[currentDepth] = []; - } - - objectsByDepth[currentDepth].Add(value); - } - - // Recursively collect nested objects - if (PropertyInjectionCache.HasInjectableProperties(value.GetType())) - { - CollectNestedObjects(value, objectsByDepth, visitedObjects, currentDepth + 1); - } - } - } - } - - /// - /// Clears the initialization cache. Should be called at the end of test sessions. - /// - public void ClearCache() - { - _initializationTasks.Clear(); - } -} diff --git a/TUnit.Engine/Services/EventReceiverOrchestrator.cs b/TUnit.Engine/Services/EventReceiverOrchestrator.cs index 33b1e12871..0d127feb35 100644 --- a/TUnit.Engine/Services/EventReceiverOrchestrator.cs +++ b/TUnit.Engine/Services/EventReceiverOrchestrator.cs @@ -2,9 +2,9 @@ using System.Runtime.CompilerServices; using TUnit.Core; using TUnit.Core.Data; +using TUnit.Core.Enums; using TUnit.Core.Helpers; using TUnit.Core.Interfaces; -using TUnit.Core.Tracking; using TUnit.Engine.Events; using TUnit.Engine.Extensions; using TUnit.Engine.Logging; @@ -16,7 +16,6 @@ internal sealed class EventReceiverOrchestrator : IDisposable { private readonly EventReceiverRegistry _registry = new(); private readonly TUnitFrameworkLogger _logger; - private readonly TrackableObjectGraphProvider _trackableObjectGraphProvider; // Track which assemblies/classes/sessions have had their "first" event invoked private ThreadSafeDictionary _firstTestInAssemblyTasks = new(); @@ -34,10 +33,9 @@ internal sealed class EventReceiverOrchestrator : IDisposable // Track registered First event receiver types to avoid duplicate registrations private readonly ConcurrentHashSet _registeredFirstEventReceiverTypes = new(); - public EventReceiverOrchestrator(TUnitFrameworkLogger logger, TrackableObjectGraphProvider trackableObjectGraphProvider) + public EventReceiverOrchestrator(TUnitFrameworkLogger logger) { _logger = logger; - _trackableObjectGraphProvider = trackableObjectGraphProvider; } public void RegisterReceivers(TestContext context, CancellationToken cancellationToken) @@ -87,7 +85,7 @@ obj is IFirstTestInAssemblyEventReceiver || // Fast-path checks with inlining [MethodImpl(MethodImplOptions.AggressiveInlining)] - public async ValueTask InvokeTestStartEventReceiversAsync(TestContext context, CancellationToken cancellationToken) + public async ValueTask InvokeTestStartEventReceiversAsync(TestContext context, CancellationToken cancellationToken, EventReceiverStage? stage = null) { // Fast path - no allocation if no receivers if (!_registry.HasTestStartReceivers()) @@ -95,10 +93,10 @@ public async ValueTask InvokeTestStartEventReceiversAsync(TestContext context, C return; } - await InvokeTestStartEventReceiversCore(context, cancellationToken); + await InvokeTestStartEventReceiversCore(context, cancellationToken, stage); } - private async ValueTask InvokeTestStartEventReceiversCore(TestContext context, CancellationToken cancellationToken) + private async ValueTask InvokeTestStartEventReceiversCore(TestContext context, CancellationToken cancellationToken, EventReceiverStage? stage) { // Manual filtering and sorting instead of LINQ to avoid allocations var eligibleObjects = context.GetEligibleEventObjects(); @@ -108,6 +106,13 @@ private async ValueTask InvokeTestStartEventReceiversCore(TestContext context, C { if (obj is ITestStartEventReceiver receiver) { +#if NET + // Filter by stage if specified (only on .NET 8.0+ where Stage property exists) + if (stage.HasValue && receiver.Stage != stage.Value) + { + continue; + } +#endif receivers ??= []; receivers.Add(receiver); } @@ -130,17 +135,17 @@ private async ValueTask InvokeTestStartEventReceiversCore(TestContext context, C } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public async ValueTask> InvokeTestEndEventReceiversAsync(TestContext context, CancellationToken cancellationToken) + public async ValueTask> InvokeTestEndEventReceiversAsync(TestContext context, CancellationToken cancellationToken, EventReceiverStage? stage = null) { if (!_registry.HasTestEndReceivers()) { return []; } - return await InvokeTestEndEventReceiversCore(context, cancellationToken); + return await InvokeTestEndEventReceiversCore(context, cancellationToken, stage); } - private async ValueTask> InvokeTestEndEventReceiversCore(TestContext context, CancellationToken cancellationToken) + private async ValueTask> InvokeTestEndEventReceiversCore(TestContext context, CancellationToken cancellationToken, EventReceiverStage? stage) { var exceptions = new List(); @@ -152,6 +157,13 @@ private async ValueTask> InvokeTestEndEventReceiversCore(TestCon { if (obj is ITestEndEventReceiver receiver) { +#if NET + // Filter by stage if specified (only on .NET 8.0+ where Stage property exists) + if (stage.HasValue && receiver.Stage != stage.Value) + { + continue; + } +#endif receivers ??= []; receivers.Add(receiver); } diff --git a/TUnit.Engine/Services/HookExecutor.cs b/TUnit.Engine/Services/HookExecutor.cs index 46a58414a6..5421310afd 100644 --- a/TUnit.Engine/Services/HookExecutor.cs +++ b/TUnit.Engine/Services/HookExecutor.cs @@ -57,7 +57,7 @@ public async ValueTask ExecuteBeforeTestSessionHooksAsync(CancellationToken canc ExceptionDispatchInfo.Capture(skipEx).Throw(); } - throw new BeforeTestSessionException("BeforeTestSession hook failed", ex); + throw new BeforeTestSessionException($"BeforeTestSession hook failed: {ex.Message}", ex); } } } @@ -83,7 +83,7 @@ public async ValueTask> ExecuteAfterTestSessionHooksAsync(Cancel { // Collect hook exceptions instead of throwing immediately // This allows all hooks to run even if some fail - exceptions.Add(new AfterTestSessionException("AfterTestSession hook failed", ex)); + exceptions.Add(new AfterTestSessionException($"AfterTestSession hook failed: {ex.Message}", ex)); } } @@ -119,7 +119,7 @@ public async ValueTask ExecuteBeforeAssemblyHooksAsync(Assembly assembly, Cancel ExceptionDispatchInfo.Capture(skipEx).Throw(); } - throw new BeforeAssemblyException("BeforeAssembly hook failed", ex); + throw new BeforeAssemblyException($"BeforeAssembly hook failed: {ex.Message}", ex); } } } @@ -146,7 +146,7 @@ public async ValueTask> ExecuteAfterAssemblyHooksAsync(Assembly { // Collect hook exceptions instead of throwing immediately // This allows all hooks to run even if some fail - exceptions.Add(new AfterAssemblyException("AfterAssembly hook failed", ex)); + exceptions.Add(new AfterAssemblyException($"AfterAssembly hook failed: {ex.Message}", ex)); } } @@ -184,7 +184,7 @@ public async ValueTask ExecuteBeforeClassHooksAsync( ExceptionDispatchInfo.Capture(skipEx).Throw(); } - throw new BeforeClassException("BeforeClass hook failed", ex); + throw new BeforeClassException($"BeforeClass hook failed: {ex.Message}", ex); } } } @@ -213,7 +213,7 @@ public async ValueTask> ExecuteAfterClassHooksAsync( { // Collect hook exceptions instead of throwing immediately // This allows all hooks to run even if some fail - exceptions.Add(new AfterClassException("AfterClass hook failed", ex)); + exceptions.Add(new AfterClassException($"AfterClass hook failed: {ex.Message}", ex)); } } @@ -248,7 +248,7 @@ public async ValueTask ExecuteBeforeTestHooksAsync(AbstractExecutableTest test, ExceptionDispatchInfo.Capture(skipEx).Throw(); } - throw new BeforeTestException("BeforeEveryTest hook failed", ex); + throw new BeforeTestException($"BeforeEveryTest hook failed: {ex.Message}", ex); } } } @@ -277,7 +277,7 @@ public async ValueTask ExecuteBeforeTestHooksAsync(AbstractExecutableTest test, ExceptionDispatchInfo.Capture(skipEx).Throw(); } - throw new BeforeTestException("BeforeTest hook failed", ex); + throw new BeforeTestException($"BeforeTest hook failed: {ex.Message}", ex); } } } @@ -347,7 +347,7 @@ public async ValueTask ExecuteBeforeTestDiscoveryHooksAsync(CancellationToken ca } catch (Exception ex) { - throw new BeforeTestDiscoveryException("BeforeTestDiscovery hook failed", ex); + throw new BeforeTestDiscoveryException($"BeforeTestDiscovery hook failed: {ex.Message}", ex); } } } @@ -370,7 +370,7 @@ public async ValueTask ExecuteAfterTestDiscoveryHooksAsync(CancellationToken can } catch (Exception ex) { - throw new AfterTestDiscoveryException("AfterTestDiscovery hook failed", ex); + throw new AfterTestDiscoveryException($"AfterTestDiscovery hook failed: {ex.Message}", ex); } } } diff --git a/TUnit.Engine/Services/ObjectGraphDiscoveryService.cs b/TUnit.Engine/Services/ObjectGraphDiscoveryService.cs new file mode 100644 index 0000000000..1a0636f605 --- /dev/null +++ b/TUnit.Engine/Services/ObjectGraphDiscoveryService.cs @@ -0,0 +1,45 @@ +using TUnit.Core; +using TUnit.Core.Discovery; +using TUnit.Core.Interfaces; + +namespace TUnit.Engine.Services; + +/// +/// Service for discovering and organizing object graphs in TUnit.Engine. +/// Delegates to in TUnit.Core for the actual discovery logic. +/// +internal sealed class ObjectGraphDiscoveryService +{ + private readonly IObjectGraphDiscoverer _discoverer; + + /// + /// Creates a new instance with the default discoverer. + /// + public ObjectGraphDiscoveryService() : this(new ObjectGraphDiscoverer()) + { + } + + /// + /// Creates a new instance with a custom discoverer (for testing). + /// + public ObjectGraphDiscoveryService(IObjectGraphDiscoverer discoverer) + { + _discoverer = discoverer; + } + + /// + /// Discovers all objects from test context arguments and properties, organized by depth level. + /// + public IObjectGraph DiscoverObjectGraph(TestContext testContext, CancellationToken cancellationToken = default) + { + return _discoverer.DiscoverObjectGraph(testContext, cancellationToken); + } + + /// + /// Discovers nested objects from a single root object, organized by depth. + /// + public IObjectGraph DiscoverNestedObjectGraph(object rootObject, CancellationToken cancellationToken = default) + { + return _discoverer.DiscoverNestedObjectGraph(rootObject, cancellationToken); + } +} diff --git a/TUnit.Engine/Services/ObjectLifecycleService.cs b/TUnit.Engine/Services/ObjectLifecycleService.cs new file mode 100644 index 0000000000..8ab1773f6a --- /dev/null +++ b/TUnit.Engine/Services/ObjectLifecycleService.cs @@ -0,0 +1,464 @@ +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using TUnit.Core; +using TUnit.Core.Helpers; +using TUnit.Core.Interfaces; +using TUnit.Core.PropertyInjection; +using TUnit.Core.PropertyInjection.Initialization; +using TUnit.Core.Tracking; + +namespace TUnit.Engine.Services; + +/// +/// Unified service for managing object lifecycle. +/// Orchestrates: registration, property injection, initialization (IAsyncInitializer), and tracking. +/// Replaces the fragmented: PropertyInjectionService, DataSourceInitializer, PropertyInitializationOrchestrator, +/// PropertyDataResolver, and ObjectRegistrationService. +/// +/// Uses Lazy<T> for dependencies to break circular references without manual Initialize() calls. +/// Follows clear phase separation: Register → Inject → Initialize → Cleanup. +/// +/// +/// Implements to allow PropertyInjector to call back for initialization +/// without creating a direct dependency (breaking the circular reference pattern). +/// +internal sealed class ObjectLifecycleService : IObjectRegistry, IInitializationCallback +{ + private readonly Lazy _propertyInjector; + private readonly ObjectGraphDiscoveryService _objectGraphDiscoveryService; + private readonly ObjectTracker _objectTracker; + + // Track initialization state per object + // Use ReferenceEqualityComparer to prevent objects with custom Equals from sharing initialization state + private readonly ConcurrentDictionary> _initializationTasks = + new(Core.Helpers.ReferenceEqualityComparer.Instance); + + public ObjectLifecycleService( + Lazy propertyInjector, + ObjectGraphDiscoveryService objectGraphDiscoveryService, + ObjectTracker objectTracker) + { + _propertyInjector = propertyInjector; + _objectGraphDiscoveryService = objectGraphDiscoveryService; + _objectTracker = objectTracker; + } + + private PropertyInjector PropertyInjector => _propertyInjector.Value; + + #region Phase 1: Registration (Discovery Time) + + /// + /// Registers a test for lifecycle management during discovery. + /// Resolves and caches property values (to create shared objects early) without setting them on the placeholder instance. + /// Tracks the resolved objects so reference counting works correctly across all tests. + /// Does NOT call IAsyncInitializer (deferred to execution). + /// + public async Task RegisterTestAsync(TestContext testContext) + { + var objectBag = testContext.StateBag.Items; + var methodMetadata = testContext.Metadata.TestDetails.MethodMetadata; + var events = testContext.InternalEvents; + var testClassType = testContext.Metadata.TestDetails.ClassType; + + // Resolve property values (creating shared objects) and cache them WITHOUT setting on placeholder instance + // This ensures shared objects are created once and tracked with the correct reference count + await PropertyInjector.ResolveAndCachePropertiesAsync(testClassType, objectBag, methodMetadata, events, testContext); + + // Track the cached objects so they get the correct reference count + _objectTracker.TrackObjects(testContext); + } + + /// + /// IObjectRegistry implementation - registers a single object. + /// Injects properties but does NOT call IAsyncInitializer (deferred to execution). + /// + public async Task RegisterObjectAsync( + object instance, + ConcurrentDictionary objectBag, + MethodMetadata? methodMetadata, + TestContextEvents events) + { + if (instance == null) + { + throw new ArgumentNullException(nameof(instance)); + } + + // Inject properties during registration + await PropertyInjector.InjectPropertiesAsync(instance, objectBag, methodMetadata, events); + } + + /// + /// IObjectRegistry implementation - registers multiple argument objects. + /// + public async Task RegisterArgumentsAsync( + object?[] arguments, + ConcurrentDictionary objectBag, + MethodMetadata? methodMetadata, + TestContextEvents events) + { + if (arguments == null || arguments.Length == 0) + { + return; + } + + // Pre-allocate with expected capacity to avoid resizing + var tasks = new List(arguments.Length); + foreach (var argument in arguments) + { + if (argument != null) + { + tasks.Add(RegisterObjectAsync(argument, objectBag, methodMetadata, events)); + } + } + + if (tasks.Count > 0) + { + await Task.WhenAll(tasks); + } + } + + #endregion + + #region Phase 2: Preparation (Execution Time) + + /// + /// Prepares a test for execution. + /// Sets already-resolved cached property values on the current instance. + /// This is needed because retries create new instances that don't have properties set yet. + /// Does NOT call IAsyncInitializer - that is deferred until after BeforeClass hooks via InitializeTestObjectsAsync. + /// + public void PrepareTest(TestContext testContext) + { + var testClassInstance = testContext.Metadata.TestDetails.ClassInstance; + + // Set already-cached property values on the current instance + // Properties were resolved and cached during RegisterTestAsync, so shared objects are already created + // We just need to set them on the actual test instance (retries create new instances) + SetCachedPropertiesOnInstance(testClassInstance, testContext); + } + + /// + /// Initializes test objects (IAsyncInitializer) after BeforeClass hooks have run. + /// This ensures resources like Docker containers are not started until needed. + /// + public async Task InitializeTestObjectsAsync(TestContext testContext, CancellationToken cancellationToken) + { + // Initialize all tracked objects (IAsyncInitializer) depth-first + await InitializeTrackedObjectsAsync(testContext, cancellationToken); + } + + /// + /// Sets already-cached property values on a test class instance. + /// This is used to apply cached property values to new instances created during retries. + /// + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Reflection mode is not used in AOT")] + private void SetCachedPropertiesOnInstance(object instance, TestContext testContext) + { + var plan = PropertyInjectionCache.GetOrCreatePlan(instance.GetType()); + + if (!plan.HasProperties) + { + return; + } + + var cachedProperties = testContext.Metadata.TestDetails.TestClassInjectedPropertyArguments; + + if (plan.SourceGeneratedProperties.Length > 0) + { + foreach (var metadata in plan.SourceGeneratedProperties) + { + var cacheKey = PropertyCacheKeyGenerator.GetCacheKey(metadata); + + if (cachedProperties.TryGetValue(cacheKey, out var cachedValue) && cachedValue != null) + { + // Set the cached value on the new instance + metadata.SetProperty(instance, cachedValue); + } + } + } + else if (plan.ReflectionProperties.Length > 0) + { + foreach (var (property, _) in plan.ReflectionProperties) + { + var cacheKey = PropertyCacheKeyGenerator.GetCacheKey(property); + + if (cachedProperties.TryGetValue(cacheKey, out var cachedValue) && cachedValue != null) + { + // Set the cached value on the new instance + var setter = PropertySetterFactory.CreateSetter(property); + setter(instance, cachedValue); + } + } + } + } + + /// + /// Initializes all tracked objects depth-first (deepest objects first). + /// This is called during test execution (after BeforeClass hooks) to initialize IAsyncInitializer objects. + /// Objects at the same level are initialized in parallel. + /// + private async Task InitializeTrackedObjectsAsync(TestContext testContext, CancellationToken cancellationToken) + { + // Get levels without LINQ - use Array.Sort with reverse comparison for descending order + var trackedObjects = testContext.TrackedObjects; + var levelCount = trackedObjects.Count; + + if (levelCount > 0) + { + var levels = new int[levelCount]; + trackedObjects.Keys.CopyTo(levels, 0); + Array.Sort(levels, (a, b) => b.CompareTo(a)); // Descending order + + foreach (var level in levels) + { + if (!trackedObjects.TryGetValue(level, out var objectsAtLevel)) + { + continue; + } + + // Copy to array under lock to prevent concurrent modification + object[] objectsCopy; + lock (objectsAtLevel) + { + objectsCopy = new object[objectsAtLevel.Count]; + objectsAtLevel.CopyTo(objectsCopy); + } + + // Initialize all objects at this level in parallel + var tasks = new List(objectsCopy.Length); + foreach (var obj in objectsCopy) + { + tasks.Add(InitializeObjectWithNestedAsync(obj, cancellationToken)); + } + + if (tasks.Count > 0) + { + await Task.WhenAll(tasks); + } + } + } + + // Finally initialize the test class and its nested objects + var classInstance = testContext.Metadata.TestDetails.ClassInstance; + await InitializeNestedObjectsForExecutionAsync(classInstance, cancellationToken); + await ObjectInitializer.InitializeAsync(classInstance, cancellationToken); + } + + /// + /// Initializes an object and its nested objects. + /// + private async Task InitializeObjectWithNestedAsync(object obj, CancellationToken cancellationToken) + { + // First initialize nested objects depth-first + await InitializeNestedObjectsForExecutionAsync(obj, cancellationToken); + + // Then initialize the object itself + await ObjectInitializer.InitializeAsync(obj, cancellationToken); + } + + /// + /// Initializes nested objects during execution phase - all IAsyncInitializer objects. + /// + private Task InitializeNestedObjectsForExecutionAsync(object rootObject, CancellationToken cancellationToken) + { + return InitializeNestedObjectsAsync( + rootObject, + ObjectInitializer.InitializeAsync, + cancellationToken); + } + + #endregion + + #region Phase 3: Object Initialization + + /// + /// Injects properties into an object without calling IAsyncInitializer. + /// Used during test discovery to prepare data sources without triggering async initialization. + /// + public async ValueTask InjectPropertiesAsync( + T obj, + ConcurrentDictionary? objectBag = null, + MethodMetadata? methodMetadata = null, + TestContextEvents? events = null) where T : notnull + { + if (obj == null) + { + throw new ArgumentNullException(nameof(obj)); + } + + objectBag ??= new ConcurrentDictionary(); + events ??= new TestContextEvents(); + + // Only inject properties, do not call IAsyncInitializer + await PropertyInjector.InjectPropertiesAsync(obj, objectBag, methodMetadata, events); + + return obj; + } + + /// + /// Ensures an object is fully initialized (property injection + IAsyncInitializer). + /// Thread-safe with fast-path for already-initialized objects. + /// Called during test execution to initialize all IAsyncInitializer objects. + /// + public async ValueTask EnsureInitializedAsync( + T obj, + ConcurrentDictionary? objectBag = null, + MethodMetadata? methodMetadata = null, + TestContextEvents? events = null, + CancellationToken cancellationToken = default) where T : notnull + { + if (obj == null) + { + throw new ArgumentNullException(nameof(obj)); + } + + // Fast path: already processed by this service + if (_initializationTasks.TryGetValue(obj, out var existingTcs) && existingTcs.Task.IsCompleted) + { + if (existingTcs.Task.IsFaulted) + { + await existingTcs.Task.ConfigureAwait(false); + } + + // EnsureInitializedAsync is only called during discovery (from PropertyInjector). + // If the object is shared and has already been processed, just return it. + // Regular IAsyncInitializer objects will be initialized during execution via InitializeTrackedObjectsAsync. + return obj; + } + + // Slow path: need to initialize + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + existingTcs = _initializationTasks.GetOrAdd(obj, tcs); + + if (existingTcs == tcs) + { + try + { + await InitializeObjectCoreAsync(obj, objectBag, methodMetadata, events, cancellationToken); + tcs.SetResult(true); + } + catch (OperationCanceledException) + { + // Propagate cancellation without caching failure - allows retry after cancel + _initializationTasks.TryRemove(obj, out _); + tcs.SetCanceled(); + throw; + } + catch (Exception ex) + { + // Remove failed initialization from cache to allow retry + // This is important for transient failures that may succeed on retry + _initializationTasks.TryRemove(obj, out _); + tcs.SetException(ex); + throw; + } + } + else + { + await existingTcs.Task.ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + } + + return obj; + } + + /// + /// Core initialization: property injection + IAsyncDiscoveryInitializer only. + /// Regular IAsyncInitializer objects are NOT initialized here - they are deferred to execution phase. + /// + private async Task InitializeObjectCoreAsync( + object obj, + ConcurrentDictionary? objectBag, + MethodMetadata? methodMetadata, + TestContextEvents? events, + CancellationToken cancellationToken) + { + objectBag ??= new ConcurrentDictionary(); + events ??= new TestContextEvents(); + + // Let exceptions propagate naturally - don't wrap in InvalidOperationException + // This aligns with ObjectInitializer behavior and provides cleaner stack traces + + // Step 1: Inject properties + await PropertyInjector.InjectPropertiesAsync(obj, objectBag, methodMetadata, events); + + // Step 2: Initialize nested objects depth-first (discovery-only) + await InitializeNestedObjectsForDiscoveryAsync(obj, cancellationToken); + + // Step 3: Call IAsyncDiscoveryInitializer only (not regular IAsyncInitializer) + // Regular IAsyncInitializer objects are deferred to execution phase via InitializeTestObjectsAsync + await ObjectInitializer.InitializeForDiscoveryAsync(obj, cancellationToken); + } + + /// + /// Initializes nested objects during discovery phase - only IAsyncDiscoveryInitializer objects. + /// + private Task InitializeNestedObjectsForDiscoveryAsync(object rootObject, CancellationToken cancellationToken) + { + return InitializeNestedObjectsAsync( + rootObject, + ObjectInitializer.InitializeForDiscoveryAsync, + cancellationToken); + } + + /// + /// Shared implementation for nested object initialization (DRY). + /// Discovers nested objects and initializes them depth-first using the provided initializer. + /// + /// The root object to discover nested objects from. + /// The initializer function to call for each object. + /// Cancellation token. + private async Task InitializeNestedObjectsAsync( + object rootObject, + Func initializer, + CancellationToken cancellationToken) + { + var graph = _objectGraphDiscoveryService.DiscoverNestedObjectGraph(rootObject, cancellationToken); + + // Initialize from deepest to shallowest (skip depth 0 which is the root itself) + foreach (var depth in graph.GetDepthsDescending()) + { + if (depth == 0) + { + continue; // Root handled separately + } + + var objectsAtDepth = graph.GetObjectsAtDepth(depth); + + // Pre-allocate task list without LINQ Select + var tasks = new List(); + foreach (var obj in objectsAtDepth) + { + tasks.Add(initializer(obj, cancellationToken).AsTask()); + } + + if (tasks.Count > 0) + { + await Task.WhenAll(tasks); + } + } + } + + #endregion + + #region Phase 4: Cleanup + + /// + /// Cleans up after test execution. + /// Decrements reference counts and disposes objects when count reaches zero. + /// + public async Task CleanupTestAsync(TestContext testContext, List cleanupExceptions) + { + await _objectTracker.UntrackObjects(testContext, cleanupExceptions); + } + + #endregion + + /// + /// Clears the initialization cache. Called at end of test session. + /// + public void ClearCache() + { + _initializationTasks.Clear(); + } +} diff --git a/TUnit.Engine/Services/ObjectRegistrationService.cs b/TUnit.Engine/Services/ObjectRegistrationService.cs deleted file mode 100644 index 26b8bc2bbe..0000000000 --- a/TUnit.Engine/Services/ObjectRegistrationService.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System.Collections.Concurrent; -using System.Diagnostics.CodeAnalysis; -using TUnit.Core; -using TUnit.Core.PropertyInjection; -using TUnit.Core.Tracking; - -namespace TUnit.Engine.Services; - -/// -/// Handles object registration during the test discovery/registration phase. -/// Responsibilities: Create instances, inject properties, track for disposal (ONCE per object). -/// Does NOT call IAsyncInitializer - that's deferred to ObjectInitializationService during execution. -/// -internal sealed class ObjectRegistrationService : IObjectRegistry -{ - private readonly PropertyInjectionService _propertyInjectionService; - - public ObjectRegistrationService( - PropertyInjectionService propertyInjectionService) - { - _propertyInjectionService = propertyInjectionService ?? throw new ArgumentNullException(nameof(propertyInjectionService)); - } - - /// - /// Registers a single object during the registration phase. - /// Injects properties, tracks for disposal (once), but does NOT call IAsyncInitializer. - /// - /// The object instance to register. Must not be null. - /// Shared object bag for the test context. Must not be null. - /// Method metadata for the test. Can be null. - /// Test context events for tracking. Must not be null and must be unique per test permutation. - public async Task RegisterObjectAsync( - object instance, - ConcurrentDictionary objectBag, - MethodMetadata? methodMetadata, - TestContextEvents events) - { - if (instance == null) - { - throw new ArgumentNullException(nameof(instance)); - } - - if (objectBag == null) - { - throw new ArgumentNullException(nameof(objectBag)); - } - - if (events == null) - { - throw new ArgumentNullException(nameof(events), "TestContextEvents must not be null. Each test permutation must have a unique TestContextEvents instance for proper disposal tracking."); - } - - if (RequiresPropertyInjection(instance)) - { - await _propertyInjectionService.InjectPropertiesIntoObjectAsync( - instance, - objectBag, - methodMetadata, - events); - } - } - - /// - /// Registers multiple objects (e.g., constructor/method arguments) in parallel. - /// Used during test registration to prepare arguments without executing expensive operations. - /// - public async Task RegisterArgumentsAsync( - object?[] arguments, - ConcurrentDictionary objectBag, - MethodMetadata? methodMetadata, - TestContextEvents events) - { - if (arguments == null || arguments.Length == 0) - { - return; - } - - // Process arguments in parallel for performance - var tasks = new List(); - foreach (var argument in arguments) - { - if (argument != null) - { - tasks.Add(RegisterObjectAsync(argument, objectBag, methodMetadata, events)); - } - } - - await Task.WhenAll(tasks); - } - - /// - /// Determines if an object requires property injection. - /// - #if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Property injection cache handles both AOT and reflection modes appropriately")] - #endif - private bool RequiresPropertyInjection(object instance) - { - return PropertyInjectionCache.HasInjectableProperties(instance.GetType()); - } -} diff --git a/TUnit.Engine/Services/PropertyDataResolver.cs b/TUnit.Engine/Services/PropertyDataResolver.cs deleted file mode 100644 index bd50c6d5fc..0000000000 --- a/TUnit.Engine/Services/PropertyDataResolver.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System.Collections.Concurrent; -using System.Diagnostics.CodeAnalysis; -using TUnit.Core; -using TUnit.Core.Helpers; -using TUnit.Core.Interfaces; -using TUnit.Core.PropertyInjection; -using TUnit.Core.PropertyInjection.Initialization; - -namespace TUnit.Engine.Services; - -/// -/// Handles all data source resolution logic for property initialization. -/// Follows Single Responsibility Principle by focusing only on data resolution. -/// -internal static class PropertyDataResolver -{ - /// - /// Resolves data from a property's data source. - /// -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Property data resolution uses reflection on property types")] -#endif - public static async Task ResolvePropertyDataAsync(PropertyInitializationContext context, DataSourceInitializer dataSourceInitializer, IObjectRegistry objectRegistry) - { - var dataSource = await GetInitializedDataSourceAsync(context, dataSourceInitializer); - if (dataSource == null) - { - return null; - } - - var dataGeneratorMetadata = CreateDataGeneratorMetadata(context, dataSource); - var dataRows = dataSource.GetDataRowsAsync(dataGeneratorMetadata); - - // Get the first value from the data source - await foreach (var factory in dataRows) - { - var args = await factory(); - var value = ResolveValueFromArgs(context.PropertyType, args); - - // Resolve any Func wrappers - value = await ResolveDelegateValue(value); - - // Initialize the resolved value if needed - if (value != null) - { - // Ensure the value is fully initialized (property injection + IAsyncInitializer) - // DataSourceInitializer handles both data sources and regular objects - if (value is IDataSourceAttribute dataSourceValue) - { - value = await dataSourceInitializer.EnsureInitializedAsync( - dataSourceValue, - context.ObjectBag, - context.MethodMetadata, - context.Events); - } - else if (PropertyInjectionCache.HasInjectableProperties(value.GetType()) || value is IAsyncInitializer) - { - value = await dataSourceInitializer.EnsureInitializedAsync( - value, - context.ObjectBag, - context.MethodMetadata, - context.Events); - } - - return value; - } - } - - return null; - } - - /// - /// Gets an initialized data source from the context. - /// Ensures the data source is fully initialized (including property injection) before returning it. - /// - private static async Task GetInitializedDataSourceAsync(PropertyInitializationContext context, DataSourceInitializer dataSourceInitializer) - { - IDataSourceAttribute? dataSource = null; - - if (context.DataSource != null) - { - dataSource = context.DataSource; - } - else if (context.SourceGeneratedMetadata != null) - { - // Create a new data source instance - dataSource = context.SourceGeneratedMetadata.CreateDataSource(); - } - - if (dataSource == null) - { - return null; - } - - // Ensure the data source is fully initialized before use - // This handles property injection and IAsyncInitializer - return await dataSourceInitializer.EnsureInitializedAsync( - dataSource, - context.ObjectBag, - context.MethodMetadata, - context.Events); - } - - /// - /// Creates data generator metadata for the property. - /// -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Data generator metadata creation uses reflection on property types")] -#endif - private static DataGeneratorMetadata CreateDataGeneratorMetadata( - PropertyInitializationContext context, - IDataSourceAttribute dataSource) - { - if (context.SourceGeneratedMetadata != null) - { - // Source-generated mode - if (context.SourceGeneratedMetadata.ContainingType == null) - { - throw new InvalidOperationException( - $"ContainingType is null for property '{context.PropertyName}'. " + - $"This may indicate an issue with source generator for type '{context.PropertyType.Name}'."); - } - - var propertyMetadata = new PropertyMetadata - { - IsStatic = false, - Name = context.PropertyName, - ClassMetadata = ClassMetadataHelper.GetOrCreateClassMetadata(context.SourceGeneratedMetadata.ContainingType), - Type = context.PropertyType, - ReflectionInfo = PropertyHelper.GetPropertyInfo(context.SourceGeneratedMetadata.ContainingType, context.PropertyName), - Getter = parent => PropertyHelper.GetPropertyInfo(context.SourceGeneratedMetadata.ContainingType, context.PropertyName).GetValue(parent!)!, - ContainingTypeMetadata = ClassMetadataHelper.GetOrCreateClassMetadata(context.SourceGeneratedMetadata.ContainingType) - }; - - return DataGeneratorMetadataCreator.CreateForPropertyInjection( - propertyMetadata, - context.MethodMetadata, - dataSource, - context.TestContext, - context.TestContext?.Metadata.TestDetails.ClassInstance, - context.Events, - context.ObjectBag); - } - else if (context.PropertyInfo != null) - { - // Reflection mode - return DataGeneratorMetadataCreator.CreateForPropertyInjection( - context.PropertyInfo, - context.PropertyInfo.DeclaringType!, - context.MethodMetadata, - dataSource, - context.TestContext, - context.Instance, - context.Events, - context.ObjectBag); - } - - throw new InvalidOperationException("Cannot create data generator metadata: no property information available"); - } - - /// - /// Resolves value from data source arguments, handling tuples. - /// -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Value resolution may create tuple types dynamically")] -#endif - private static object? ResolveValueFromArgs( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] - Type propertyType, - object?[]? args) - { - return TupleValueResolver.ResolveTupleValue(propertyType, args); - } - - /// - /// Resolves delegate values by invoking them. - /// - private static async ValueTask ResolveDelegateValue(object? value) - { - return await PropertyValueProcessor.ResolveTestDataValueAsync(typeof(object), value); - } -} diff --git a/TUnit.Engine/Services/PropertyInitializationOrchestrator.cs b/TUnit.Engine/Services/PropertyInitializationOrchestrator.cs deleted file mode 100644 index 7462566749..0000000000 --- a/TUnit.Engine/Services/PropertyInitializationOrchestrator.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System.Collections.Concurrent; -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using TUnit.Core; -using TUnit.Core.Interfaces.SourceGenerator; -using TUnit.Core.PropertyInjection; -using TUnit.Core.PropertyInjection.Initialization; - -namespace TUnit.Engine.Services; - -/// -/// Orchestrates the entire property initialization process. -/// Coordinates between different components and manages the initialization flow. -/// -internal sealed class PropertyInitializationOrchestrator -{ - internal readonly DataSourceInitializer _dataSourceInitializer; - private readonly IObjectRegistry _objectRegistry; - - public PropertyInitializationOrchestrator(DataSourceInitializer dataSourceInitializer, IObjectRegistry? objectRegistry) - { - _dataSourceInitializer = dataSourceInitializer ?? throw new ArgumentNullException(nameof(dataSourceInitializer)); - _objectRegistry = objectRegistry!; - } - - /// - /// Initializes all properties for an instance using source-generated metadata. - /// Properties are initialized in parallel for better performance. - /// - private async Task InitializeSourceGeneratedPropertiesAsync( - object instance, - PropertyInjectionMetadata[] properties, - ConcurrentDictionary objectBag, - MethodMetadata? methodMetadata, - TestContextEvents events, - ConcurrentDictionary visitedObjects) - { - if (properties.Length == 0) - { - return; - } - - var tasks = properties.Select(metadata => - InitializeSourceGeneratedPropertyAsync(instance, metadata, objectBag, methodMetadata, events, visitedObjects)); - - await Task.WhenAll(tasks); - } - - /// - /// Initializes all properties for an instance using reflection. - /// Properties are initialized in parallel for better performance. - /// -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Reflection-based property initialization uses PropertyInfo")] -#endif - private async Task InitializeReflectionPropertiesAsync( - object instance, - (PropertyInfo Property, IDataSourceAttribute DataSource)[] properties, - ConcurrentDictionary objectBag, - MethodMetadata? methodMetadata, - TestContextEvents events, - ConcurrentDictionary visitedObjects) - { - if (properties.Length == 0) - { - return; - } - - var tasks = properties.Select(pair => - InitializeReflectionPropertyAsync(instance, pair.Property, pair.DataSource, objectBag, methodMetadata, events, visitedObjects)); - - await Task.WhenAll(tasks); - } - - /// - /// Initializes a single property using source-generated metadata. - /// - #if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Property data resolver is called for source-generated properties which are AOT-safe")] - #endif - private async Task InitializeSourceGeneratedPropertyAsync( - object instance, - PropertyInjectionMetadata metadata, - ConcurrentDictionary objectBag, - MethodMetadata? methodMetadata, - TestContextEvents events, - ConcurrentDictionary visitedObjects) - { - object? resolvedValue = null; - var testContext = TestContext.Current; - - // Check if property was pre-resolved during registration - if (testContext?.Metadata.TestDetails.TestClassInjectedPropertyArguments.TryGetValue(metadata.PropertyName, out resolvedValue) == true) - { - // Use pre-resolved value - it was already initialized during first resolution - } - else - { - // Resolve the property value from the data source - resolvedValue = await PropertyDataResolver.ResolvePropertyDataAsync( - new PropertyInitializationContext - { - Instance = instance, - SourceGeneratedMetadata = metadata, - PropertyName = metadata.PropertyName, - PropertyType = metadata.PropertyType, - PropertySetter = metadata.SetProperty, - ObjectBag = objectBag, - MethodMetadata = methodMetadata, - Events = events, - VisitedObjects = visitedObjects, - TestContext = testContext, - IsNestedProperty = false - }, - _dataSourceInitializer, - _objectRegistry); - - if (resolvedValue == null) - { - return; - } - } - - // Set the property value - metadata.SetProperty(instance, resolvedValue); - - // Store for potential reuse (using TryAdd for thread-safe concurrent access) - if (testContext != null) - { - ((ConcurrentDictionary)testContext.Metadata.TestDetails.TestClassInjectedPropertyArguments) - .TryAdd(metadata.PropertyName, resolvedValue); - } - } - - /// - /// Initializes a single property using reflection. - /// - #if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Reflection-based property initialization is only used in reflection mode, not in AOT")] - #endif - private async Task InitializeReflectionPropertyAsync( - object instance, - PropertyInfo property, - IDataSourceAttribute dataSource, - ConcurrentDictionary objectBag, - MethodMetadata? methodMetadata, - TestContextEvents events, - ConcurrentDictionary visitedObjects) - { - var testContext = TestContext.Current; - var propertySetter = PropertySetterFactory.CreateSetter(property); - - // Resolve the property value from the data source - var resolvedValue = await PropertyDataResolver.ResolvePropertyDataAsync( - new PropertyInitializationContext - { - Instance = instance, - PropertyInfo = property, - DataSource = dataSource, - PropertyName = property.Name, - PropertyType = property.PropertyType, - PropertySetter = propertySetter, - ObjectBag = objectBag, - MethodMetadata = methodMetadata, - Events = events, - VisitedObjects = visitedObjects, - TestContext = testContext, - IsNestedProperty = false - }, - _dataSourceInitializer, - _objectRegistry); - - if (resolvedValue == null) - { - return; - } - - // Set the property value - propertySetter(instance, resolvedValue); - } - - /// - /// Handles the complete initialization flow for an object with properties. - /// - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Specific source gen path")] - public async Task InitializeObjectWithPropertiesAsync( - object instance, - PropertyInjectionPlan plan, - ConcurrentDictionary objectBag, - MethodMetadata? methodMetadata, - TestContextEvents events, - ConcurrentDictionary visitedObjects) - { - if (plan.HasProperties == false) - { - return; - } - - // Initialize properties based on the mode (source-generated or reflection) - if (SourceRegistrar.IsEnabled) - { - await InitializeSourceGeneratedPropertiesAsync( - instance, plan.SourceGeneratedProperties, objectBag, methodMetadata, events, visitedObjects); - } - else - { - await InitializeReflectionPropertiesAsync( - instance, plan.ReflectionProperties, objectBag, methodMetadata, events, visitedObjects); - } - } - -} diff --git a/TUnit.Engine/Services/PropertyInjectionService.cs b/TUnit.Engine/Services/PropertyInjectionService.cs deleted file mode 100644 index 9cdde83a2d..0000000000 --- a/TUnit.Engine/Services/PropertyInjectionService.cs +++ /dev/null @@ -1,204 +0,0 @@ -using System.Collections.Concurrent; -using TUnit.Core; -using TUnit.Core.PropertyInjection; - -namespace TUnit.Engine.Services; - -/// -/// Internal service for property injection. -/// Used by ObjectRegistrationService during registration phase. -/// -internal sealed class PropertyInjectionService -{ - private readonly DataSourceInitializer _dataSourceInitializer; - private PropertyInitializationOrchestrator _orchestrator; - - // Simple object pool for visited objects dictionaries to reduce allocations - private static readonly ConcurrentBag> _visitedObjectsPool = new(); - - public PropertyInjectionService(DataSourceInitializer dataSourceInitializer) - { - _dataSourceInitializer = dataSourceInitializer ?? throw new ArgumentNullException(nameof(dataSourceInitializer)); - _orchestrator = new PropertyInitializationOrchestrator(dataSourceInitializer, null!); - } - - /// - /// Completes initialization by providing the ObjectRegistrationService as IObjectRegistry. - /// This two-phase initialization breaks the circular dependency while maintaining type safety. - /// - public void Initialize(IObjectRegistry objectRegistry) - { - _orchestrator = new PropertyInitializationOrchestrator(_dataSourceInitializer, objectRegistry); - } - - /// - /// Injects properties with data sources into argument objects just before test execution. - /// This ensures properties are only initialized when the test is about to run. - /// Arguments are processed in parallel for better performance. - /// - public async Task InjectPropertiesIntoArgumentsAsync(object?[] arguments, ConcurrentDictionary objectBag, MethodMetadata methodMetadata, TestContextEvents events) - { - if (arguments.Length == 0) - { - return; - } - - var injectableArgs = arguments - .Where(argument => argument != null && PropertyInjectionCache.HasInjectableProperties(argument.GetType())) - .ToArray(); - - if (injectableArgs.Length == 0) - { - return; - } - - var argumentTasks = injectableArgs - .Select(argument => InjectPropertiesIntoObjectAsync(argument!, objectBag, methodMetadata, events)) - .ToArray(); - - await Task.WhenAll(argumentTasks); - } - - - /// - /// Recursively injects properties with data sources into a single object. - /// Uses source generation mode when available, falls back to reflection mode. - /// After injection, handles tracking, initialization, and recursive injection. - /// - /// The object instance to inject properties into. - /// Shared object bag for the test context. Must not be null. - /// Method metadata for the test. Can be null. - /// Test context events for tracking. Must not be null and must be unique per test permutation. - public async Task InjectPropertiesIntoObjectAsync(object instance, ConcurrentDictionary objectBag, MethodMetadata? methodMetadata, TestContextEvents events) - { - if (objectBag == null) - { - throw new ArgumentNullException(nameof(objectBag)); - } - - if (events == null) - { - throw new ArgumentNullException(nameof(events), "TestContextEvents must not be null. Each test permutation must have a unique TestContextEvents instance for proper disposal tracking."); - } - - // Rent dictionary from pool to avoid allocations - if (!_visitedObjectsPool.TryTake(out var visitedObjects)) - { -#if NETSTANDARD2_0 - visitedObjects = new ConcurrentDictionary(); -#else - visitedObjects = new ConcurrentDictionary(ReferenceEqualityComparer.Instance); -#endif - } - - try - { - await InjectPropertiesIntoObjectAsyncCore(instance, objectBag, methodMetadata, events, visitedObjects); - } - finally - { - // Clear and return to pool (reject if too large to avoid memory bloat) - visitedObjects.Clear(); - if (visitedObjects.Count == 0) - { - _visitedObjectsPool.Add(visitedObjects); - } - } - } - - internal async Task InjectPropertiesIntoObjectAsyncCore(object instance, ConcurrentDictionary objectBag, MethodMetadata? methodMetadata, TestContextEvents events, ConcurrentDictionary visitedObjects) - { - if (instance == null) - { - return; - } - - // Prevent cycles - if (!visitedObjects.TryAdd(instance, 0)) - { - return; - } - - try - { - // Use optimized single-lookup pattern with fast-path for already-injected instances - await PropertyInjectionCache.EnsureInjectedAsync(instance, async _ => - { - var plan = PropertyInjectionCache.GetOrCreatePlan(instance.GetType()); - - await _orchestrator.InitializeObjectWithPropertiesAsync( - instance, plan, objectBag, methodMetadata, events, visitedObjects); - }); - - await RecurseIntoNestedPropertiesAsync(instance, objectBag, methodMetadata, events, visitedObjects); - } - catch (Exception ex) - { - var detailedMessage = $"Failed to inject properties for type '{instance.GetType().Name}': {ex.Message}"; - - if (ex.StackTrace != null) - { - detailedMessage += $"\nStack trace: {ex.StackTrace}"; - } - - throw new InvalidOperationException(detailedMessage, ex); - } - } - - /// - /// Recursively injects properties into nested objects that have injectable properties. - /// This is called after the direct properties of an object have been initialized. - /// - private async Task RecurseIntoNestedPropertiesAsync( - object instance, - ConcurrentDictionary objectBag, - MethodMetadata? methodMetadata, - TestContextEvents events, - ConcurrentDictionary visitedObjects) - { - var plan = PropertyInjectionCache.GetOrCreatePlan(instance.GetType()); - if (!plan.HasProperties) - { - return; - } - - if (SourceRegistrar.IsEnabled) - { - foreach (var metadata in plan.SourceGeneratedProperties) - { - var property = metadata.ContainingType.GetProperty(metadata.PropertyName); - if (property == null || !property.CanRead) - { - continue; - } - - var propertyValue = property.GetValue(instance); - if (propertyValue == null) - { - continue; - } - - if (PropertyInjectionCache.HasInjectableProperties(propertyValue.GetType())) - { - await InjectPropertiesIntoObjectAsyncCore(propertyValue, objectBag, methodMetadata, events, visitedObjects); - } - } - } - else - { - foreach (var (property, _) in plan.ReflectionProperties) - { - var propertyValue = property.GetValue(instance); - if (propertyValue == null) - { - continue; - } - - if (PropertyInjectionCache.HasInjectableProperties(propertyValue.GetType())) - { - await InjectPropertiesIntoObjectAsyncCore(propertyValue, objectBag, methodMetadata, events, visitedObjects); - } - } - } - } -} diff --git a/TUnit.Engine/Services/PropertyInjector.cs b/TUnit.Engine/Services/PropertyInjector.cs new file mode 100644 index 0000000000..e0008d17ac --- /dev/null +++ b/TUnit.Engine/Services/PropertyInjector.cs @@ -0,0 +1,616 @@ +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using TUnit.Core; +using TUnit.Core.Helpers; +using TUnit.Core.Interfaces; +using TUnit.Core.Interfaces.SourceGenerator; +using TUnit.Core.PropertyInjection; +using TUnit.Core.PropertyInjection.Initialization; + +namespace TUnit.Engine.Services; + +/// +/// Pure property injection service. +/// Follows Single Responsibility Principle - only injects property values, doesn't initialize objects. +/// Uses Lazy initialization to break circular dependencies without manual Initialize() calls. +/// +/// +/// Depends on rather than a concrete service, +/// enabling testability and following Dependency Inversion Principle. +/// +internal sealed class PropertyInjector +{ + private readonly Lazy _initializationCallback; + private readonly string _testSessionId; + + // Object pool for visited dictionaries to reduce allocations + private static readonly ConcurrentBag> _visitedObjectsPool = new(); + + public PropertyInjector(Lazy initializationCallback, string testSessionId) + { + _initializationCallback = initializationCallback; + _testSessionId = testSessionId; + } + + /// + /// Resolves and caches property values for a test class type WITHOUT setting them on an instance. + /// Used during registration to create shared objects early and enable proper reference counting. + /// + public async Task ResolveAndCachePropertiesAsync( + Type testClassType, + ConcurrentDictionary objectBag, + MethodMetadata? methodMetadata, + TestContextEvents events, + TestContext testContext) + { + // Skip property resolution if this test is reusing the discovery instance (already initialized) + if (testContext.IsDiscoveryInstanceReused) + { + return; + } + + var plan = PropertyInjectionCache.GetOrCreatePlan(testClassType); + + if (!plan.HasProperties) + { + return; + } + + // Resolve properties based on what's available in the plan + if (plan.SourceGeneratedProperties.Length > 0) + { + await ResolveAndCacheSourceGeneratedPropertiesAsync( + plan.SourceGeneratedProperties, objectBag, methodMetadata, events, testContext); + } + else if (plan.ReflectionProperties.Length > 0) + { + await ResolveAndCacheReflectionPropertiesAsync( + plan.ReflectionProperties, objectBag, methodMetadata, events, testContext); + } + } + + /// + /// Injects properties into an object and recursively into nested objects. + /// + public async Task InjectPropertiesAsync( + object instance, + ConcurrentDictionary objectBag, + MethodMetadata? methodMetadata, + TestContextEvents events) + { + if (instance == null) + { + throw new ArgumentNullException(nameof(instance)); + } + + if (objectBag == null) + { + throw new ArgumentNullException(nameof(objectBag)); + } + + if (events == null) + { + throw new ArgumentNullException(nameof(events)); + } + + // Rent dictionary from pool + if (!_visitedObjectsPool.TryTake(out var visitedObjects)) + { +#if NETSTANDARD2_0 + visitedObjects = new ConcurrentDictionary(); +#else + visitedObjects = new ConcurrentDictionary(Core.Helpers.ReferenceEqualityComparer.Instance); +#endif + } + + try + { + await InjectPropertiesRecursiveAsync(instance, objectBag, methodMetadata, events, visitedObjects); + } + finally + { + visitedObjects.Clear(); + _visitedObjectsPool.Add(visitedObjects); + } + } + + /// + /// Injects properties into multiple argument objects in parallel. + /// + public async Task InjectPropertiesIntoArgumentsAsync( + object?[] arguments, + ConcurrentDictionary objectBag, + MethodMetadata methodMetadata, + TestContextEvents events) + { + if (arguments.Length == 0) + { + return; + } + + // Build list of injectable args without LINQ + var injectableArgs = new List(arguments.Length); + foreach (var arg in arguments) + { + if (arg != null && PropertyInjectionCache.HasInjectableProperties(arg.GetType())) + { + injectableArgs.Add(arg); + } + } + + if (injectableArgs.Count == 0) + { + return; + } + + // Build task list without LINQ Select + var tasks = new List(injectableArgs.Count); + foreach (var arg in injectableArgs) + { + tasks.Add(InjectPropertiesAsync(arg, objectBag, methodMetadata, events)); + } + + await Task.WhenAll(tasks); + } + + private async Task InjectPropertiesRecursiveAsync( + object instance, + ConcurrentDictionary objectBag, + MethodMetadata? methodMetadata, + TestContextEvents events, + ConcurrentDictionary visitedObjects) + { + if (instance == null) + { + return; + } + + // Prevent cycles + if (!visitedObjects.TryAdd(instance, 0)) + { + return; + } + + try + { + var plan = PropertyInjectionCache.GetOrCreatePlan(instance.GetType()); + + if (plan.HasProperties) + { + // Initialize properties based on what's available in the plan + if (plan.SourceGeneratedProperties.Length > 0) + { + await InjectSourceGeneratedPropertiesAsync( + instance, plan.SourceGeneratedProperties, objectBag, methodMetadata, events, visitedObjects); + } + else if (plan.ReflectionProperties.Length > 0) + { + await InjectReflectionPropertiesAsync( + instance, plan.ReflectionProperties, objectBag, methodMetadata, events, visitedObjects); + } + } + + // Recurse into nested properties + await RecurseIntoNestedPropertiesAsync(instance, plan, objectBag, methodMetadata, events, visitedObjects); + } + catch (Exception ex) + { + throw new InvalidOperationException( + $"Failed to inject properties for type '{instance.GetType().Name}': {ex.Message}", ex); + } + } + + private Task InjectSourceGeneratedPropertiesAsync( + object instance, + PropertyInjectionMetadata[] properties, + ConcurrentDictionary objectBag, + MethodMetadata? methodMetadata, + TestContextEvents events, + ConcurrentDictionary visitedObjects) + { + return ParallelTaskHelper.ForEachAsync(properties, + prop => InjectSourceGeneratedPropertyAsync(instance, prop, objectBag, methodMetadata, events, visitedObjects)); + } + + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Source-gen properties are AOT-safe")] + private async Task InjectSourceGeneratedPropertyAsync( + object instance, + PropertyInjectionMetadata metadata, + ConcurrentDictionary objectBag, + MethodMetadata? methodMetadata, + TestContextEvents events, + ConcurrentDictionary visitedObjects) + { + // First check if the property already has a value - skip if it does + // This handles nested objects that were already constructed with their properties set + var property = metadata.ContainingType.GetProperty(metadata.PropertyName); + if (property != null && property.CanRead) + { + var existingValue = property.GetValue(instance); + if (existingValue != null) + { + // Property already has a value, don't overwrite it + return; + } + } + + var testContext = TestContext.Current; + object? resolvedValue = null; + + // Use a composite key to avoid conflicts when nested classes have properties with the same name + var cacheKey = PropertyCacheKeyGenerator.GetCacheKey(metadata); + + // Check if property was pre-resolved during registration + if (testContext?.Metadata.TestDetails.TestClassInjectedPropertyArguments.TryGetValue(cacheKey, out resolvedValue) == true) + { + // Use pre-resolved value + } + else + { + // Resolve the property value from the data source + resolvedValue = await ResolvePropertyDataAsync( + new PropertyInitializationContext + { + Instance = instance, + SourceGeneratedMetadata = metadata, + PropertyName = metadata.PropertyName, + PropertyType = metadata.PropertyType, + PropertySetter = metadata.SetProperty, + ObjectBag = objectBag, + MethodMetadata = methodMetadata, + Events = events, + VisitedObjects = visitedObjects, + TestContext = testContext, + IsNestedProperty = false + }); + + if (resolvedValue == null) + { + return; + } + } + + // Set the property value + metadata.SetProperty(instance, resolvedValue); + + // Store for potential reuse with composite key + if (testContext != null) + { + ((ConcurrentDictionary)testContext.Metadata.TestDetails.TestClassInjectedPropertyArguments) + .TryAdd(cacheKey, resolvedValue); + } + } + + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Reflection mode is not used in AOT")] + private Task InjectReflectionPropertiesAsync( + object instance, + (PropertyInfo Property, IDataSourceAttribute DataSource)[] properties, + ConcurrentDictionary objectBag, + MethodMetadata? methodMetadata, + TestContextEvents events, + ConcurrentDictionary visitedObjects) + { + return ParallelTaskHelper.ForEachAsync(properties, + pair => InjectReflectionPropertyAsync(instance, pair.Property, pair.DataSource, objectBag, methodMetadata, events, visitedObjects)); + } + + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Reflection mode is not used in AOT")] + private async Task InjectReflectionPropertyAsync( + object instance, + PropertyInfo property, + IDataSourceAttribute dataSource, + ConcurrentDictionary objectBag, + MethodMetadata? methodMetadata, + TestContextEvents events, + ConcurrentDictionary visitedObjects) + { + var testContext = TestContext.Current; + var propertySetter = PropertySetterFactory.CreateSetter(property); + + var resolvedValue = await ResolvePropertyDataAsync( + new PropertyInitializationContext + { + Instance = instance, + PropertyInfo = property, + DataSource = dataSource, + PropertyName = property.Name, + PropertyType = property.PropertyType, + PropertySetter = propertySetter, + ObjectBag = objectBag, + MethodMetadata = methodMetadata, + Events = events, + VisitedObjects = visitedObjects, + TestContext = testContext, + IsNestedProperty = false + }); + + if (resolvedValue == null) + { + return; + } + + propertySetter(instance, resolvedValue); + } + + private async Task RecurseIntoNestedPropertiesAsync( + object instance, + PropertyInjectionPlan plan, + ConcurrentDictionary objectBag, + MethodMetadata? methodMetadata, + TestContextEvents events, + ConcurrentDictionary visitedObjects) + { + if (!plan.HasProperties) + { + return; + } + + if (plan.SourceGeneratedProperties.Length > 0) + { + foreach (var metadata in plan.SourceGeneratedProperties) + { + var property = metadata.ContainingType.GetProperty(metadata.PropertyName); + if (property == null || !property.CanRead) + { + continue; + } + + var propertyValue = property.GetValue(instance); + if (propertyValue == null) + { + continue; + } + + if (PropertyInjectionCache.HasInjectableProperties(propertyValue.GetType())) + { + await InjectPropertiesRecursiveAsync(propertyValue, objectBag, methodMetadata, events, visitedObjects); + } + } + } + else if (plan.ReflectionProperties.Length > 0) + { + foreach (var (property, _) in plan.ReflectionProperties) + { + var propertyValue = property.GetValue(instance); + if (propertyValue == null) + { + continue; + } + + if (PropertyInjectionCache.HasInjectableProperties(propertyValue.GetType())) + { + await InjectPropertiesRecursiveAsync(propertyValue, objectBag, methodMetadata, events, visitedObjects); + } + } + } + } + + private Task ResolveAndCacheSourceGeneratedPropertiesAsync( + PropertyInjectionMetadata[] properties, + ConcurrentDictionary objectBag, + MethodMetadata? methodMetadata, + TestContextEvents events, + TestContext testContext) + { + return ParallelTaskHelper.ForEachAsync(properties, + prop => ResolveAndCacheSourceGeneratedPropertyAsync(prop, objectBag, methodMetadata, events, testContext)); + } + + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Source-gen properties are AOT-safe")] + private async Task ResolveAndCacheSourceGeneratedPropertyAsync( + PropertyInjectionMetadata metadata, + ConcurrentDictionary objectBag, + MethodMetadata? methodMetadata, + TestContextEvents events, + TestContext testContext) + { + var cacheKey = PropertyCacheKeyGenerator.GetCacheKey(metadata); + + // Check if already cached + if (testContext.Metadata.TestDetails.TestClassInjectedPropertyArguments.ContainsKey(cacheKey)) + { + return; + } + + // Resolve the property value from the data source + var resolvedValue = await ResolvePropertyDataAsync( + new PropertyInitializationContext + { + Instance = PlaceholderInstance.Instance, // Use placeholder during registration + SourceGeneratedMetadata = metadata, + PropertyName = metadata.PropertyName, + PropertyType = metadata.PropertyType, + PropertySetter = metadata.SetProperty, + ObjectBag = objectBag, + MethodMetadata = methodMetadata, + Events = events, + VisitedObjects = new ConcurrentDictionary(), // Empty dictionary for cycle detection + TestContext = testContext, + IsNestedProperty = false + }); + + if (resolvedValue != null) + { + // Cache the resolved value + ((ConcurrentDictionary)testContext.Metadata.TestDetails.TestClassInjectedPropertyArguments) + .TryAdd(cacheKey, resolvedValue); + } + } + + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Reflection mode is not used in AOT")] + private Task ResolveAndCacheReflectionPropertiesAsync( + (PropertyInfo Property, IDataSourceAttribute DataSource)[] properties, + ConcurrentDictionary objectBag, + MethodMetadata? methodMetadata, + TestContextEvents events, + TestContext testContext) + { + return ParallelTaskHelper.ForEachAsync(properties, + pair => ResolveAndCacheReflectionPropertyAsync(pair.Property, pair.DataSource, objectBag, methodMetadata, events, testContext)); + } + + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Reflection mode is not used in AOT")] + private async Task ResolveAndCacheReflectionPropertyAsync( + PropertyInfo property, + IDataSourceAttribute dataSource, + ConcurrentDictionary objectBag, + MethodMetadata? methodMetadata, + TestContextEvents events, + TestContext testContext) + { + var cacheKey = PropertyCacheKeyGenerator.GetCacheKey(property); + + // Check if already cached + if (testContext.Metadata.TestDetails.TestClassInjectedPropertyArguments.ContainsKey(cacheKey)) + { + return; + } + + var propertySetter = PropertySetterFactory.CreateSetter(property); + + var resolvedValue = await ResolvePropertyDataAsync( + new PropertyInitializationContext + { + Instance = PlaceholderInstance.Instance, // Use placeholder during registration + PropertyInfo = property, + DataSource = dataSource, + PropertyName = property.Name, + PropertyType = property.PropertyType, + PropertySetter = propertySetter, + ObjectBag = objectBag, + MethodMetadata = methodMetadata, + Events = events, + VisitedObjects = new ConcurrentDictionary(), // Empty dictionary for cycle detection + TestContext = testContext, + IsNestedProperty = false + }); + + if (resolvedValue != null) + { + // Cache the resolved value + ((ConcurrentDictionary)testContext.Metadata.TestDetails.TestClassInjectedPropertyArguments) + .TryAdd(cacheKey, resolvedValue); + } + } + + /// + /// Resolves data from a property's data source. + /// + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Property data resolution handles both modes")] + [UnconditionalSuppressMessage("Trimming", "IL2072", Justification = "PropertyType is properly preserved through source generation")] + private async Task ResolvePropertyDataAsync(PropertyInitializationContext context) + { + var dataSource = await GetInitializedDataSourceAsync(context); + if (dataSource == null) + { + return null; + } + + var dataGeneratorMetadata = CreateDataGeneratorMetadata(context, dataSource); + var dataRows = dataSource.GetDataRowsAsync(dataGeneratorMetadata); + + await foreach (var factory in dataRows) + { + var args = await factory(); + var value = TupleValueResolver.ResolveTupleValue(context.PropertyType, args); + + // Resolve any Func wrappers + value = await PropertyValueProcessor.ResolveTestDataValueAsync(typeof(object), value); + + if (value != null) + { + // EnsureInitializedAsync handles property injection and initialization. + // ObjectInitializer is phase-aware: during Discovery phase, only IAsyncDiscoveryInitializer + // objects are initialized; regular IAsyncInitializer objects are deferred to Execution phase. + await _initializationCallback.Value.EnsureInitializedAsync( + value, + context.ObjectBag, + context.MethodMetadata, + context.Events); + + return value; + } + } + + return null; + } + + private async Task GetInitializedDataSourceAsync(PropertyInitializationContext context) + { + IDataSourceAttribute? dataSource = null; + + if (context.DataSource != null) + { + dataSource = context.DataSource; + } + else if (context.SourceGeneratedMetadata != null) + { + dataSource = context.SourceGeneratedMetadata.CreateDataSource(); + } + + if (dataSource == null) + { + return null; + } + + // Ensure the data source is initialized + return await _initializationCallback.Value.EnsureInitializedAsync( + dataSource, + context.ObjectBag, + context.MethodMetadata, + context.Events); + } + + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Metadata creation handles both modes")] + [UnconditionalSuppressMessage("Trimming", "IL2072", Justification = "ContainingType and PropertyType are preserved through source generation")] + private DataGeneratorMetadata CreateDataGeneratorMetadata( + PropertyInitializationContext context, + IDataSourceAttribute dataSource) + { + if (context.SourceGeneratedMetadata != null) + { + if (context.SourceGeneratedMetadata.ContainingType == null) + { + throw new InvalidOperationException( + $"ContainingType is null for property '{context.PropertyName}'."); + } + + var propertyMetadata = new PropertyMetadata + { + IsStatic = false, + Name = context.PropertyName, + ClassMetadata = ClassMetadataHelper.GetOrCreateClassMetadata(context.SourceGeneratedMetadata.ContainingType), + Type = context.PropertyType, + ReflectionInfo = PropertyHelper.GetPropertyInfo(context.SourceGeneratedMetadata.ContainingType, context.PropertyName), + Getter = parent => PropertyHelper.GetPropertyInfo(context.SourceGeneratedMetadata.ContainingType, context.PropertyName).GetValue(parent!)!, + ContainingTypeMetadata = ClassMetadataHelper.GetOrCreateClassMetadata(context.SourceGeneratedMetadata.ContainingType) + }; + + return DataGeneratorMetadataCreator.CreateForPropertyInjection( + propertyMetadata, + context.MethodMetadata, + dataSource, + _testSessionId, + context.TestContext, + context.TestContext?.Metadata.TestDetails.ClassInstance, + context.Events, + context.ObjectBag); + } + else if (context.PropertyInfo != null) + { + return DataGeneratorMetadataCreator.CreateForPropertyInjection( + context.PropertyInfo, + context.PropertyInfo.DeclaringType!, + context.MethodMetadata, + dataSource, + _testSessionId, + context.TestContext, + context.Instance, + context.Events, + context.ObjectBag); + } + + throw new InvalidOperationException("Cannot create data generator metadata: no property information available"); + } +} diff --git a/TUnit.Engine/Services/TestArgumentRegistrationService.cs b/TUnit.Engine/Services/TestArgumentRegistrationService.cs index dd9c4e10c5..407df90e4e 100644 --- a/TUnit.Engine/Services/TestArgumentRegistrationService.cs +++ b/TUnit.Engine/Services/TestArgumentRegistrationService.cs @@ -1,160 +1,48 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using TUnit.Core; -using TUnit.Core.Data; -using TUnit.Core.Enums; -using TUnit.Core.Interfaces; -using TUnit.Core.Interfaces.SourceGenerator; -using TUnit.Core.PropertyInjection; -using TUnit.Core.Tracking; namespace TUnit.Engine.Services; /// -/// Service that handles registration of test arguments (constructor args, method args) during test discovery. -/// Implements ITestRegisteredEventReceiver to register objects when tests are registered. -/// Renamed from TestArgumentTrackingService to clarify it's for the registration phase. +/// Internal service that handles registration of test arguments during test discovery. +/// Not a user-extensibility point - called directly by TestBuilder. +/// Simplified to use ObjectLifecycleService for all object registration. /// -internal sealed class TestArgumentRegistrationService : ITestRegisteredEventReceiver +internal sealed class TestArgumentRegistrationService { - private readonly ObjectRegistrationService _objectRegistrationService; - private readonly ObjectTracker _objectTracker; + private readonly ObjectLifecycleService _objectLifecycleService; - public TestArgumentRegistrationService(ObjectRegistrationService objectRegistrationService, ObjectTracker objectTracker) + public TestArgumentRegistrationService(ObjectLifecycleService objectLifecycleService) { - _objectRegistrationService = objectRegistrationService; - _objectTracker = objectTracker; + _objectLifecycleService = objectLifecycleService; } - public int Order => int.MinValue; // Run first to ensure registration happens before other event receivers - /// - /// Called when a test is registered. This is the correct time to register constructor and method arguments + /// Called when a test is registered. Registers constructor and method arguments /// for proper reference counting and disposal tracking. + /// Property values are resolved lazily during test execution (not during discovery). /// - public async ValueTask OnTestRegistered(TestRegisteredContext context) + public async ValueTask RegisterTestArgumentsAsync(TestContext testContext) { - var testContext = context.TestContext; + TestContext.Current = testContext; + var classArguments = testContext.Metadata.TestDetails.TestClassArguments; var methodArguments = testContext.Metadata.TestDetails.TestMethodArguments; - // Register class arguments (registration phase - property injection + tracking, NO IAsyncInitializer) - await _objectRegistrationService.RegisterArgumentsAsync( + // Register class arguments (property injection during registration) + await _objectLifecycleService.RegisterArgumentsAsync( classArguments, testContext.StateBag.Items, testContext.Metadata.TestDetails.MethodMetadata, testContext.InternalEvents); - // Register method arguments (registration phase) - await _objectRegistrationService.RegisterArgumentsAsync( + // Register method arguments + await _objectLifecycleService.RegisterArgumentsAsync( methodArguments, testContext.StateBag.Items, testContext.Metadata.TestDetails.MethodMetadata, testContext.InternalEvents); - // Register properties that will be injected into the test class - await RegisterPropertiesAsync(testContext); - - _objectTracker.TrackObjects(testContext); - } - - /// - /// Registers properties that will be injected into the test class instance. - /// This ensures proper reference counting for all property-injected instances during discovery. - /// Exceptions during data generation will be caught and associated with the test for reporting. - /// - private async ValueTask RegisterPropertiesAsync(TestContext testContext) - { - try - { - var classType = testContext.Metadata.TestDetails.ClassType; - - // Get the property source for the class - var propertySource = PropertySourceRegistry.GetSource(classType); - if (propertySource?.ShouldInitialize != true) - { - // No properties to inject for this class - return; - } - - // Get all properties that need injection - var propertyMetadata = propertySource.GetPropertyMetadata(); - - foreach (var metadata in propertyMetadata) - { - try - { - // Create the data source for this property - var dataSource = metadata.CreateDataSource(); - - // Create minimal DataGeneratorMetadata for property resolution during registration - var testBuilderContext = new TestBuilderContext - { - TestMetadata = testContext.Metadata.TestDetails.MethodMetadata, - DataSourceAttribute = dataSource, - Events = testContext.InternalEvents, - StateBag = testContext.StateBag.Items - }; - - var dataGenMetadata = new DataGeneratorMetadata - { - TestBuilderContext = new TestBuilderContextAccessor(testBuilderContext), - MembersToGenerate = [], // Properties don't use member generation - TestInformation = testContext.Metadata.TestDetails.MethodMetadata, - Type = DataGeneratorType.Property, - TestSessionId = TestSessionContext.Current?.Id ?? "registration", - TestClassInstance = null, // Not available during registration - ClassInstanceArguments = testContext.Metadata.TestDetails.TestClassArguments - }; - - // Get the data rows from the data source - var dataRows = dataSource.GetDataRowsAsync(dataGenMetadata); - - // Get the first data row (properties get single values, not multiple) - await foreach (var dataRowFunc in dataRows) - { - var dataRow = await dataRowFunc(); - if (dataRow is { Length: > 0 }) - { - var data = dataRow[0]; - - if (data != null) - { - // Store for later injection - testContext.Metadata.TestDetails.TestClassInjectedPropertyArguments[metadata.PropertyName] = data; - - // Register the ClassDataSource instance during registration phase - // This does: property injection + tracking (NO IAsyncInitializer - deferred to execution) - await _objectRegistrationService.RegisterObjectAsync( - data, - testContext.StateBag.Items, - testContext.Metadata.TestDetails.MethodMetadata, - testContext.InternalEvents); - } - } - break; // Only take the first result for property injection - } - } - catch (Exception ex) - { - // Capture the exception for this property and re-throw - // The test building process will handle marking it as failed - var exceptionMessage = $"Failed to generate data for property '{metadata.PropertyName}': {ex.Message}"; - var propertyException = new InvalidOperationException(exceptionMessage, ex); - throw propertyException; - } - } - } - catch (Exception ex) - { - // Capture any top-level exceptions (e.g., getting property source) and re-throw - // The test building process will handle marking it as failed - var exceptionMessage = $"Failed to register properties for test '{testContext.Metadata.TestDetails.TestName}': {ex.Message}"; - var registrationException = new InvalidOperationException(exceptionMessage, ex); - throw registrationException; - } + // Register the test for tracking (inject properties and track objects for disposal) + await _objectLifecycleService.RegisterTestAsync(testContext); } } diff --git a/TUnit.Engine/Services/TestExecution/TestCoordinator.cs b/TUnit.Engine/Services/TestExecution/TestCoordinator.cs index c34181a332..683a9e8f4c 100644 --- a/TUnit.Engine/Services/TestExecution/TestCoordinator.cs +++ b/TUnit.Engine/Services/TestExecution/TestCoordinator.cs @@ -90,7 +90,7 @@ private async ValueTask ExecuteTestInternalAsync(AbstractExecutableTest test, Ca } // Ensure TestSession hooks run before creating test instances - await _testExecutor.EnsureTestSessionHooksExecutedAsync().ConfigureAwait(false); + await _testExecutor.EnsureTestSessionHooksExecutedAsync(cancellationToken).ConfigureAwait(false); // Execute test with retry logic - each retry gets a fresh instance // Timeout is applied per retry attempt, not across all retries @@ -126,9 +126,9 @@ await TimeoutHelper.ExecuteWithTimeoutAsync( try { - await _testInitializer.InitializeTest(test, ct).ConfigureAwait(false); + _testInitializer.PrepareTest(test, ct); test.Context.RestoreExecutionContext(); - await _testExecutor.ExecuteAsync(test, ct).ConfigureAwait(false); + await _testExecutor.ExecuteAsync(test, _testInitializer, ct).ConfigureAwait(false); } finally { diff --git a/TUnit.Engine/Services/TestFilterService.cs b/TUnit.Engine/Services/TestFilterService.cs index ee091095fd..0c0285ba62 100644 --- a/TUnit.Engine/Services/TestFilterService.cs +++ b/TUnit.Engine/Services/TestFilterService.cs @@ -74,7 +74,7 @@ private async Task RegisterTest(AbstractExecutableTest test) try { - await testArgumentRegistrationService.OnTestRegistered(registeredContext); + await testArgumentRegistrationService.RegisterTestArgumentsAsync(test.Context); } catch (Exception ex) { diff --git a/TUnit.Engine/Services/VerbosityService.cs b/TUnit.Engine/Services/VerbosityService.cs index 18c36aacc2..f297c026bd 100644 --- a/TUnit.Engine/Services/VerbosityService.cs +++ b/TUnit.Engine/Services/VerbosityService.cs @@ -1,7 +1,6 @@ using Microsoft.Testing.Platform.CommandLine; using Microsoft.Testing.Platform.Services; using TUnit.Engine.CommandLineProviders; -using TUnit.Engine.Helpers; using LogLevel = TUnit.Core.Logging.LogLevel; #pragma warning disable TPEXP @@ -42,8 +41,6 @@ public string CreateVerbositySummary() $"(Stack traces: {ShowDetailedStackTrace}, "; } - // Use centralized environment variable cache - private static bool GetOutputLevel(ICommandLineOptions commandLineOptions, IServiceProvider serviceProvider) { // Check for --output flag (Microsoft.Testing.Platform extension) @@ -64,8 +61,8 @@ private static LogLevel GetLogLevel(ICommandLineOptions commandLineOptions) return LogLevelCommandProvider.ParseLogLevel(args); } - // Check cached legacy environment variable for backwards compatibility - if (EnvironmentVariableCache.Get("TUNIT_DISCOVERY_DIAGNOSTICS") == "1") + // Check legacy environment variable for backwards compatibility + if (Environment.GetEnvironmentVariable("TUNIT_DISCOVERY_DIAGNOSTICS") == "1") { return LogLevel.Debug; } diff --git a/TUnit.Engine/TestExecutor.cs b/TUnit.Engine/TestExecutor.cs index bc1ab31272..dbb80cc3f6 100644 --- a/TUnit.Engine/TestExecutor.cs +++ b/TUnit.Engine/TestExecutor.cs @@ -2,6 +2,7 @@ using System.Reflection; using System.Runtime.ExceptionServices; using TUnit.Core; +using TUnit.Core.Enums; using TUnit.Core.Exceptions; using TUnit.Core.Interfaces; using TUnit.Core.Services; @@ -18,6 +19,7 @@ internal class TestExecutor private readonly HookExecutor _hookExecutor; private readonly TestLifecycleCoordinator _lifecycleCoordinator; private readonly BeforeHookTaskCache _beforeHookTaskCache; + private readonly AfterHookPairTracker _afterHookPairTracker; private readonly IContextProvider _contextProvider; private readonly EventReceiverOrchestrator _eventReceiverOrchestrator; @@ -25,12 +27,14 @@ public TestExecutor( HookExecutor hookExecutor, TestLifecycleCoordinator lifecycleCoordinator, BeforeHookTaskCache beforeHookTaskCache, + AfterHookPairTracker afterHookPairTracker, IContextProvider contextProvider, EventReceiverOrchestrator eventReceiverOrchestrator) { _hookExecutor = hookExecutor; _lifecycleCoordinator = lifecycleCoordinator; _beforeHookTaskCache = beforeHookTaskCache; + _afterHookPairTracker = afterHookPairTracker; _contextProvider = contextProvider; _eventReceiverOrchestrator = eventReceiverOrchestrator; } @@ -39,19 +43,26 @@ public TestExecutor( /// /// Ensures that Before(TestSession) hooks have been executed. /// This is called before creating test instances to ensure resources are available. + /// Registers the corresponding After(TestSession) hook to run on cancellation. /// - public async Task EnsureTestSessionHooksExecutedAsync() + public async Task EnsureTestSessionHooksExecutedAsync(CancellationToken cancellationToken) { // Get or create and cache Before hooks - these run only once - await _beforeHookTaskCache.GetOrCreateBeforeTestSessionTask(() => - _hookExecutor.ExecuteBeforeTestSessionHooksAsync(CancellationToken.None)).ConfigureAwait(false); + await _beforeHookTaskCache.GetOrCreateBeforeTestSessionTask( + ct => _hookExecutor.ExecuteBeforeTestSessionHooksAsync(ct), + cancellationToken).ConfigureAwait(false); + + // Register After Session hook to run on cancellation (guarantees cleanup) + _afterHookPairTracker.RegisterAfterTestSessionHook( + cancellationToken, + () => new ValueTask>(_hookExecutor.ExecuteAfterTestSessionHooksAsync(CancellationToken.None).AsTask())); } /// /// Creates a test executor delegate that wraps the provided executor with hook orchestration. /// Uses focused services that follow SRP to manage lifecycle and execution. /// - public async ValueTask ExecuteAsync(AbstractExecutableTest executableTest, CancellationToken cancellationToken) + public async ValueTask ExecuteAsync(AbstractExecutableTest executableTest, TestInitializer testInitializer, CancellationToken cancellationToken) { var testClass = executableTest.Metadata.TestClassType; @@ -62,7 +73,7 @@ public async ValueTask ExecuteAsync(AbstractExecutableTest executableTest, Cance try { - await EnsureTestSessionHooksExecutedAsync().ConfigureAwait(false); + await EnsureTestSessionHooksExecutedAsync(cancellationToken).ConfigureAwait(false); await _eventReceiverOrchestrator.InvokeFirstTestInSessionEventReceiversAsync( executableTest.Context, @@ -71,8 +82,16 @@ await _eventReceiverOrchestrator.InvokeFirstTestInSessionEventReceiversAsync( executableTest.Context.ClassContext.AssemblyContext.TestSessionContext.RestoreExecutionContext(); - await _beforeHookTaskCache.GetOrCreateBeforeAssemblyTask(testAssembly, assembly => _hookExecutor.ExecuteBeforeAssemblyHooksAsync(assembly, CancellationToken.None)) - .ConfigureAwait(false); + await _beforeHookTaskCache.GetOrCreateBeforeAssemblyTask( + testAssembly, + (assembly, ct) => _hookExecutor.ExecuteBeforeAssemblyHooksAsync(assembly, ct), + cancellationToken).ConfigureAwait(false); + + // Register After Assembly hook to run on cancellation (guarantees cleanup) + _afterHookPairTracker.RegisterAfterAssemblyHook( + testAssembly, + cancellationToken, + (assembly) => new ValueTask>(_hookExecutor.ExecuteAfterAssemblyHooksAsync(assembly, CancellationToken.None).AsTask())); await _eventReceiverOrchestrator.InvokeFirstTestInAssemblyEventReceiversAsync( executableTest.Context, @@ -81,8 +100,10 @@ await _eventReceiverOrchestrator.InvokeFirstTestInAssemblyEventReceiversAsync( executableTest.Context.ClassContext.AssemblyContext.RestoreExecutionContext(); - await _beforeHookTaskCache.GetOrCreateBeforeClassTask(testClass, _ => _hookExecutor.ExecuteBeforeClassHooksAsync(testClass, CancellationToken.None)) - .ConfigureAwait(false); + await _beforeHookTaskCache.GetOrCreateBeforeClassTask(testClass, _hookExecutor, cancellationToken).ConfigureAwait(false); + + // Register After Class hook to run on cancellation (guarantees cleanup) + _afterHookPairTracker.RegisterAfterClassHook(testClass, _hookExecutor, cancellationToken); await _eventReceiverOrchestrator.InvokeFirstTestInClassEventReceiversAsync( executableTest.Context, @@ -91,9 +112,21 @@ await _eventReceiverOrchestrator.InvokeFirstTestInClassEventReceiversAsync( executableTest.Context.ClassContext.RestoreExecutionContext(); + // Initialize test objects (IAsyncInitializer) AFTER BeforeClass hooks + // This ensures resources like Docker containers are not started until needed + await testInitializer.InitializeTestObjectsAsync(executableTest, cancellationToken).ConfigureAwait(false); + + executableTest.Context.RestoreExecutionContext(); + + // Early stage test start receivers run before instance-level hooks + await _eventReceiverOrchestrator.InvokeTestStartEventReceiversAsync(executableTest.Context, cancellationToken, EventReceiverStage.Early).ConfigureAwait(false); + + executableTest.Context.RestoreExecutionContext(); + await _hookExecutor.ExecuteBeforeTestHooksAsync(executableTest, cancellationToken).ConfigureAwait(false); - await _eventReceiverOrchestrator.InvokeTestStartEventReceiversAsync(executableTest.Context, cancellationToken).ConfigureAwait(false); + // Late stage test start receivers run after instance-level hooks (default behavior) + await _eventReceiverOrchestrator.InvokeTestStartEventReceiversAsync(executableTest.Context, cancellationToken, EventReceiverStage.Late).ConfigureAwait(false); executableTest.Context.RestoreExecutionContext(); @@ -114,8 +147,21 @@ await _eventReceiverOrchestrator.InvokeFirstTestInClassEventReceiversAsync( } finally { - var hookExceptions = await _hookExecutor.ExecuteAfterTestHooksAsync(executableTest, cancellationToken).ConfigureAwait(false); - var eventReceiverExceptions = await _eventReceiverOrchestrator.InvokeTestEndEventReceiversAsync(executableTest.Context, cancellationToken).ConfigureAwait(false); + // After hooks must use CancellationToken.None to ensure cleanup runs even when cancelled + // This matches the pattern used for After Class/Assembly hooks in TestCoordinator + + // Early stage test end receivers run before instance-level hooks + var earlyStageExceptions = await _eventReceiverOrchestrator.InvokeTestEndEventReceiversAsync(executableTest.Context, CancellationToken.None, EventReceiverStage.Early).ConfigureAwait(false); + + var hookExceptions = await _hookExecutor.ExecuteAfterTestHooksAsync(executableTest, CancellationToken.None).ConfigureAwait(false); + + // Late stage test end receivers run after instance-level hooks (default behavior) + var lateStageExceptions = await _eventReceiverOrchestrator.InvokeTestEndEventReceiversAsync(executableTest.Context, CancellationToken.None, EventReceiverStage.Late).ConfigureAwait(false); + + // Combine all exceptions from event receivers + var eventReceiverExceptions = new List(earlyStageExceptions.Count + lateStageExceptions.Count); + eventReceiverExceptions.AddRange(earlyStageExceptions); + eventReceiverExceptions.AddRange(lateStageExceptions); if (hookExceptions.Count > 0 || eventReceiverExceptions.Count > 0) { @@ -160,6 +206,9 @@ private static async ValueTask ExecuteTestAsync(AbstractExecutableTest executabl // Set the test start time when we actually begin executing the test executableTest.Context.TestStart = DateTimeOffset.UtcNow; + // Set the cancellation token on the context so source-generated tests can access it + executableTest.Context.CancellationToken = cancellationToken; + if (executableTest.Context.InternalDiscoveredTest?.TestExecutor is { } testExecutor) { await testExecutor.ExecuteTest(executableTest.Context, @@ -181,13 +230,17 @@ internal async Task> ExecuteAfterClassAssemblyHooks(AbstractExec if (flags.ShouldExecuteAfterClass) { - var classExceptions = await _hookExecutor.ExecuteAfterClassHooksAsync(testClass, cancellationToken).ConfigureAwait(false); + // Use AfterHookPairTracker to prevent double execution if already triggered by cancellation + var classExceptions = await _afterHookPairTracker.GetOrCreateAfterClassTask(testClass, _hookExecutor, cancellationToken).ConfigureAwait(false); exceptions.AddRange(classExceptions); } if (flags.ShouldExecuteAfterAssembly) { - var assemblyExceptions = await _hookExecutor.ExecuteAfterAssemblyHooksAsync(testAssembly, cancellationToken).ConfigureAwait(false); + // Use AfterHookPairTracker to prevent double execution if already triggered by cancellation + var assemblyExceptions = await _afterHookPairTracker.GetOrCreateAfterAssemblyTask( + testAssembly, + (assembly) => new ValueTask>(_hookExecutor.ExecuteAfterAssemblyHooksAsync(assembly, cancellationToken).AsTask())).ConfigureAwait(false); exceptions.AddRange(assemblyExceptions); } @@ -197,10 +250,15 @@ internal async Task> ExecuteAfterClassAssemblyHooks(AbstractExec /// /// Execute session-level after hooks once at the end of test execution. /// Returns any exceptions that occurred during hook execution. + /// Uses AfterHookPairTracker to prevent double execution if already triggered by cancellation. /// public async Task> ExecuteAfterTestSessionHooksAsync(CancellationToken cancellationToken) { - return await _hookExecutor.ExecuteAfterTestSessionHooksAsync(cancellationToken).ConfigureAwait(false); + // Use AfterHookPairTracker to prevent double execution if already triggered by cancellation + var exceptions = await _afterHookPairTracker.GetOrCreateAfterTestSessionTask( + () => new ValueTask>(_hookExecutor.ExecuteAfterTestSessionHooksAsync(cancellationToken).AsTask())).ConfigureAwait(false); + + return exceptions; } /// diff --git a/TUnit.Engine/TestInitializer.cs b/TUnit.Engine/TestInitializer.cs index 2c8e0c7156..73fbb7fbc2 100644 --- a/TUnit.Engine/TestInitializer.cs +++ b/TUnit.Engine/TestInitializer.cs @@ -1,56 +1,38 @@ -using System.Collections.Concurrent; using TUnit.Core; -using TUnit.Core.Tracking; -using TUnit.Engine.Extensions; using TUnit.Engine.Services; namespace TUnit.Engine; +/// +/// Simplified test initializer that delegates to ObjectLifecycleService. +/// Follows Single Responsibility Principle - only coordinates test initialization. +/// internal class TestInitializer { private readonly EventReceiverOrchestrator _eventReceiverOrchestrator; - private readonly PropertyInjectionService _propertyInjectionService; - private readonly ObjectTracker _objectTracker; + private readonly ObjectLifecycleService _objectLifecycleService; - public TestInitializer(EventReceiverOrchestrator eventReceiverOrchestrator, - PropertyInjectionService propertyInjectionService, - ObjectTracker objectTracker) + public TestInitializer( + EventReceiverOrchestrator eventReceiverOrchestrator, + ObjectLifecycleService objectLifecycleService) { _eventReceiverOrchestrator = eventReceiverOrchestrator; - _propertyInjectionService = propertyInjectionService; - _objectTracker = objectTracker; + _objectLifecycleService = objectLifecycleService; } - public async ValueTask InitializeTest(AbstractExecutableTest test, CancellationToken cancellationToken) + public void PrepareTest(AbstractExecutableTest test, CancellationToken cancellationToken) { - var testClassInstance = test.Context.Metadata.TestDetails.ClassInstance; - - await _propertyInjectionService.InjectPropertiesIntoObjectAsync( - testClassInstance, - test.Context.StateBag.Items, - test.Context.Metadata.TestDetails.MethodMetadata, - test.Context.InternalEvents); - + // Register event receivers _eventReceiverOrchestrator.RegisterReceivers(test.Context, cancellationToken); - // Shouldn't retrack already tracked objects, but will track any new ones created during retries / initialization - _objectTracker.TrackObjects(test.Context); - - await InitializeTrackedObjects(test.Context, cancellationToken); + // Prepare test: set cached property values on the instance + // Does NOT call IAsyncInitializer - that is deferred until after BeforeClass hooks + _objectLifecycleService.PrepareTest(test.Context); } - private async Task InitializeTrackedObjects(TestContext testContext, CancellationToken cancellationToken) + public async ValueTask InitializeTestObjectsAsync(AbstractExecutableTest test, CancellationToken cancellationToken) { - // Initialize by level (deepest first), with objects at the same level in parallel - var levels = testContext.TrackedObjects.Keys.OrderByDescending(level => level); - - foreach (var level in levels) - { - var objectsAtLevel = testContext.TrackedObjects[level]; - await Task.WhenAll(objectsAtLevel.Select(obj => ObjectInitializer.InitializeAsync(obj, cancellationToken).AsTask())); - } - - // Finally, ensure the test class itself is initialized - await ObjectInitializer.InitializeAsync(testContext.Metadata.TestDetails.ClassInstance, cancellationToken); + // Initialize test objects (IAsyncInitializer) - called after BeforeClass hooks + await _objectLifecycleService.InitializeTestObjectsAsync(test.Context, cancellationToken); } } diff --git a/TUnit.Engine/Xml/JUnitXmlWriter.cs b/TUnit.Engine/Xml/JUnitXmlWriter.cs new file mode 100644 index 0000000000..1c9156d241 --- /dev/null +++ b/TUnit.Engine/Xml/JUnitXmlWriter.cs @@ -0,0 +1,357 @@ +using System.Globalization; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Text; +using System.Xml; +using Microsoft.Testing.Platform.Extensions.Messages; + +namespace TUnit.Engine.Xml; + +internal static class JUnitXmlWriter +{ + public static string GenerateXml( + IEnumerable testUpdates, + string? filter) + { + // Get the last state for each test + var lastStates = GetLastStates(testUpdates); + + if (lastStates.Count == 0) + { + return string.Empty; + } + + // Calculate summary statistics + var summary = CalculateSummary(lastStates.Values); + + // Get assembly and framework information + var assemblyName = Assembly.GetEntryAssembly()?.GetName().Name ?? "TestResults"; + var targetFramework = Assembly.GetExecutingAssembly() + .GetCustomAttributes() + .SingleOrDefault() + ?.FrameworkDisplayName + ?? RuntimeInformation.FrameworkDescription; + + using var stringWriter = new Utf8StringWriter(); + var settings = new XmlWriterSettings + { + Indent = true, + Encoding = Encoding.UTF8, + OmitXmlDeclaration = false, + NewLineOnAttributes = false + }; + + using var xmlWriter = XmlWriter.Create(stringWriter, settings); + + // Write XML structure + xmlWriter.WriteStartDocument(); + + // + xmlWriter.WriteStartElement("testsuites"); + xmlWriter.WriteAttributeString("name", assemblyName); + xmlWriter.WriteAttributeString("tests", summary.Total.ToString(CultureInfo.InvariantCulture)); + xmlWriter.WriteAttributeString("failures", summary.Failures.ToString(CultureInfo.InvariantCulture)); + xmlWriter.WriteAttributeString("errors", summary.Errors.ToString(CultureInfo.InvariantCulture)); + xmlWriter.WriteAttributeString("skipped", summary.Skipped.ToString(CultureInfo.InvariantCulture)); + xmlWriter.WriteAttributeString("time", summary.TotalTime.TotalSeconds.ToString("F3", CultureInfo.InvariantCulture)); + xmlWriter.WriteAttributeString("timestamp", summary.Timestamp.ToString("o", CultureInfo.InvariantCulture)); + + // Write test suite + WriteTestSuite(xmlWriter, lastStates.Values, assemblyName, targetFramework, summary, filter); + + xmlWriter.WriteEndElement(); // testsuites + xmlWriter.WriteEndDocument(); + xmlWriter.Flush(); + return stringWriter.ToString(); + } + + private static void WriteTestSuite( + XmlWriter writer, + IEnumerable tests, + string assemblyName, + string targetFramework, + TestSummary summary, + string? filter) + { + writer.WriteStartElement("testsuite"); + writer.WriteAttributeString("name", assemblyName); + writer.WriteAttributeString("tests", summary.Total.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("failures", summary.Failures.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("errors", summary.Errors.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("skipped", summary.Skipped.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("time", summary.TotalTime.TotalSeconds.ToString("F3", CultureInfo.InvariantCulture)); + writer.WriteAttributeString("timestamp", summary.Timestamp.ToString("o", CultureInfo.InvariantCulture)); + writer.WriteAttributeString("hostname", Environment.MachineName); + + // Write properties + WriteProperties(writer, targetFramework, filter); + + // Write test cases + foreach (var test in tests) + { + WriteTestCase(writer, test); + } + + writer.WriteEndElement(); // testsuite + } + + private static void WriteProperties(XmlWriter writer, string targetFramework, string? filter) + { + writer.WriteStartElement("properties"); + + writer.WriteStartElement("property"); + writer.WriteAttributeString("name", "framework"); + writer.WriteAttributeString("value", targetFramework); + writer.WriteEndElement(); + + writer.WriteStartElement("property"); + writer.WriteAttributeString("name", "platform"); + writer.WriteAttributeString("value", RuntimeInformation.OSDescription); + writer.WriteEndElement(); + + writer.WriteStartElement("property"); + writer.WriteAttributeString("name", "runtime"); + writer.WriteAttributeString("value", RuntimeInformation.FrameworkDescription); + writer.WriteEndElement(); + + if (!string.IsNullOrEmpty(filter)) + { + writer.WriteStartElement("property"); + writer.WriteAttributeString("name", "filter"); + writer.WriteAttributeString("value", filter); + writer.WriteEndElement(); + } + + writer.WriteEndElement(); // properties + } + + private static void WriteTestCase(XmlWriter writer, TestNodeUpdateMessage test) + { + var testNode = test.TestNode; + + // Get test state + var stateProperty = testNode.Properties.AsEnumerable() + .OfType() + .FirstOrDefault(); + + // Get timing + var timingProperty = testNode.Properties.AsEnumerable() + .OfType() + .FirstOrDefault(); + + var duration = timingProperty?.GlobalTiming.Duration is { } d ? d : TimeSpan.Zero; + + // Get class and method names + var testMethodIdentifier = testNode.Properties.AsEnumerable() + .OfType() + .FirstOrDefault(); + + var className = testMethodIdentifier?.TypeName ?? "UnknownClass"; + var testName = testNode.DisplayName; + + // Write testcase element + writer.WriteStartElement("testcase"); + writer.WriteAttributeString("name", testName); + writer.WriteAttributeString("classname", className); + writer.WriteAttributeString("time", duration.TotalSeconds.ToString("F3", CultureInfo.InvariantCulture)); + + // Write state-specific child elements + switch (stateProperty) + { + case FailedTestNodeStateProperty failed: + WriteFailure(writer, failed); + break; + + case ErrorTestNodeStateProperty error: + WriteError(writer, error); + break; + + case TimeoutTestNodeStateProperty timeout: + WriteTimeoutError(writer, timeout); + break; + + case CancelledTestNodeStateProperty: + WriteCancellationError(writer); + break; + + case SkippedTestNodeStateProperty skipped: + WriteSkipped(writer, skipped); + break; + + case InProgressTestNodeStateProperty: + WriteInProgressError(writer); + break; + + case PassedTestNodeStateProperty: + // No child element for passed tests + break; + } + + writer.WriteEndElement(); // testcase + } + + private static void WriteFailure(XmlWriter writer, FailedTestNodeStateProperty failed) + { + writer.WriteStartElement("failure"); + + var exception = failed.Exception; + if (exception != null) + { + writer.WriteAttributeString("message", exception.Message); + writer.WriteAttributeString("type", exception.GetType().FullName ?? "AssertionException"); + writer.WriteString(exception.ToString()); + } + else + { + var message = failed.Explanation ?? "Test failed"; + writer.WriteAttributeString("message", message); + writer.WriteAttributeString("type", "TestFailedException"); + writer.WriteString(message); + } + + writer.WriteEndElement(); // failure + } + + private static void WriteError(XmlWriter writer, ErrorTestNodeStateProperty error) + { + writer.WriteStartElement("error"); + + var exception = error.Exception; + if (exception != null) + { + writer.WriteAttributeString("message", exception.Message); + writer.WriteAttributeString("type", exception.GetType().FullName ?? "Exception"); + writer.WriteString(exception.ToString()); + } + else + { + var message = error.Explanation ?? "Test error"; + writer.WriteAttributeString("message", message); + writer.WriteAttributeString("type", "TestErrorException"); + writer.WriteString(message); + } + + writer.WriteEndElement(); // error + } + + private static void WriteTimeoutError(XmlWriter writer, TimeoutTestNodeStateProperty timeout) + { + writer.WriteStartElement("error"); + var message = timeout.Explanation ?? "Test timed out"; + writer.WriteAttributeString("message", message); + writer.WriteAttributeString("type", "TimeoutException"); + writer.WriteString(message); + writer.WriteEndElement(); // error + } + + private static void WriteCancellationError(XmlWriter writer) + { + writer.WriteStartElement("error"); + writer.WriteAttributeString("message", "Test was cancelled"); + writer.WriteAttributeString("type", "CancelledException"); + writer.WriteString("Test was cancelled"); + writer.WriteEndElement(); // error + } + + private static void WriteInProgressError(XmlWriter writer) + { + writer.WriteStartElement("error"); + writer.WriteAttributeString("message", "Test never finished"); + writer.WriteAttributeString("type", "InProgressException"); + writer.WriteString("Test never finished"); + writer.WriteEndElement(); // error + } + + private static void WriteSkipped(XmlWriter writer, SkippedTestNodeStateProperty skipped) + { + writer.WriteStartElement("skipped"); + var message = skipped.Explanation ?? "Test skipped"; + writer.WriteAttributeString("message", message); + writer.WriteString(message); + writer.WriteEndElement(); // skipped + } + + private static Dictionary GetLastStates( + IEnumerable tests) + { + var lastStates = new Dictionary(); + + foreach (var test in tests) + { + lastStates[test.TestNode.Uid.Value] = test; + } + + return lastStates; + } + + private static TestSummary CalculateSummary(IEnumerable tests) + { + DateTimeOffset? earliestStartTime = null; + + var summary = new TestSummary(); + + foreach (var test in tests) + { + summary.Total++; + + var stateProperty = test.TestNode.Properties.AsEnumerable() + .OfType() + .FirstOrDefault(); + + var timing = test.TestNode.Properties.AsEnumerable() + .OfType() + .FirstOrDefault(); + + if (timing?.GlobalTiming.Duration is { } durationValue) + { + summary.TotalTime += durationValue; + + // Track the earliest start time from actual test execution + if (timing.GlobalTiming.StartTime is { } startTime) + { + if (earliestStartTime is null || startTime < earliestStartTime) + { + earliestStartTime = startTime; + } + } + } + + switch (stateProperty) + { + case FailedTestNodeStateProperty: + summary.Failures++; + break; + case ErrorTestNodeStateProperty: + case TimeoutTestNodeStateProperty: + case CancelledTestNodeStateProperty: + case InProgressTestNodeStateProperty: + summary.Errors++; + break; + case SkippedTestNodeStateProperty: + summary.Skipped++; + break; + } + } + + // Use earliest test start time, fallback to current time if no timing data available + summary.Timestamp = earliestStartTime ?? DateTimeOffset.Now; + + return summary; + } +} + +file sealed class Utf8StringWriter : StringWriter +{ + public override Encoding Encoding => Encoding.UTF8; +} + +internal sealed class TestSummary +{ + public int Total { get; set; } + public int Failures { get; set; } + public int Errors { get; set; } + public int Skipped { get; set; } + public TimeSpan TotalTime { get; set; } + public DateTimeOffset Timestamp { get; set; } +} diff --git a/TUnit.Example.Asp.Net.TestProject/TUnit.Example.Asp.Net.TestProject.csproj b/TUnit.Example.Asp.Net.TestProject/TUnit.Example.Asp.Net.TestProject.csproj index 4106a2cedb..abc7a1390f 100644 --- a/TUnit.Example.Asp.Net.TestProject/TUnit.Example.Asp.Net.TestProject.csproj +++ b/TUnit.Example.Asp.Net.TestProject/TUnit.Example.Asp.Net.TestProject.csproj @@ -3,7 +3,7 @@ - net9.0;net10.0 + net10.0 diff --git a/TUnit.Example.Asp.Net/TUnit.Example.Asp.Net.csproj b/TUnit.Example.Asp.Net/TUnit.Example.Asp.Net.csproj index 69d9f6d009..549f17e876 100644 --- a/TUnit.Example.Asp.Net/TUnit.Example.Asp.Net.csproj +++ b/TUnit.Example.Asp.Net/TUnit.Example.Asp.Net.csproj @@ -1,7 +1,7 @@ - net9.0;net10.0 + net10.0 enable enable false diff --git a/TUnit.Pipeline/Modules/Abstract/TestBaseModule.cs b/TUnit.Pipeline/Modules/Abstract/TestBaseModule.cs index 0943197e35..481e4167a3 100644 --- a/TUnit.Pipeline/Modules/Abstract/TestBaseModule.cs +++ b/TUnit.Pipeline/Modules/Abstract/TestBaseModule.cs @@ -14,7 +14,7 @@ protected virtual IEnumerable TestableFrameworks { get { - yield return "net9.0"; + yield return "net10.0"; yield return "net8.0"; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) diff --git a/TUnit.Pipeline/Modules/PublishNugetTesterAOTModule.cs b/TUnit.Pipeline/Modules/PublishNugetTesterAOTModule.cs index 5bbb404429..b15f0e8a03 100644 --- a/TUnit.Pipeline/Modules/PublishNugetTesterAOTModule.cs +++ b/TUnit.Pipeline/Modules/PublishNugetTesterAOTModule.cs @@ -32,8 +32,8 @@ protected override Task ShouldSkip(IPipelineContext context) .FindFile(x => x.Name == "TUnit.NugetTester.csproj") .AssertExists(); - // Test AOT publishing for net8.0 and net9.0 - foreach (var framework in new[] { "net8.0", "net9.0" }) + // Test AOT publishing + foreach (var framework in new[] { "net8.0", "net9.0", "net10.0" }) { var result = await SubModule($"AOT-{framework}", async () => { diff --git a/TUnit.Pipeline/Modules/RunAspNetTestsModule.cs b/TUnit.Pipeline/Modules/RunAspNetTestsModule.cs index ad5ed5103e..e6c974434a 100644 --- a/TUnit.Pipeline/Modules/RunAspNetTestsModule.cs +++ b/TUnit.Pipeline/Modules/RunAspNetTestsModule.cs @@ -22,7 +22,7 @@ public class RunAspNetTestsModule : Module Project = project.Name, NoBuild = true, Configuration = Configuration.Release, - Framework = "net9.0", + Framework = "net10.0", WorkingDirectory = project.Folder!, Arguments = ["--ignore-exit-code", "8", "--hangdump", "--hangdump-filename", "hangdump.aspnet-tests.dmp", "--hangdump-timeout", "5m"], EnvironmentVariables = new Dictionary diff --git a/TUnit.Pipeline/Modules/RunEngineTestsModule.cs b/TUnit.Pipeline/Modules/RunEngineTestsModule.cs index 162ccaf06e..49a70177aa 100644 --- a/TUnit.Pipeline/Modules/RunEngineTestsModule.cs +++ b/TUnit.Pipeline/Modules/RunEngineTestsModule.cs @@ -33,7 +33,7 @@ public class RunEngineTestsModule : Module Project = project.Name, NoBuild = true, Configuration = Configuration.Release, - Framework = "net9.0", + Framework = "net10.0", WorkingDirectory = project.Folder!, Arguments = [ "--hangdump", "--hangdump-filename", $"hangdump.{Environment.OSVersion.Platform}.engine-tests.dmp", "--hangdump-timeout", "30m", diff --git a/TUnit.Pipeline/Modules/RunTemplateTestsModule.cs b/TUnit.Pipeline/Modules/RunTemplateTestsModule.cs index ad6f2384bd..6199c64f72 100644 --- a/TUnit.Pipeline/Modules/RunTemplateTestsModule.cs +++ b/TUnit.Pipeline/Modules/RunTemplateTestsModule.cs @@ -22,7 +22,7 @@ public class RunTemplateTestsModule : Module WorkingDirectory = project.Folder!, NoBuild = true, Configuration = Configuration.Release, - Framework = "net9.0", + Framework = "net10.0", Arguments = ["--", "--hangdump", "--hangdump-filename", "hangdump.template-tests.dmp", "--hangdump-timeout", "5m"], EnvironmentVariables = new Dictionary { diff --git a/TUnit.Pipeline/Modules/TestNugetPackageModule.cs b/TUnit.Pipeline/Modules/TestNugetPackageModule.cs index 6ace295308..e9045d5c6e 100644 --- a/TUnit.Pipeline/Modules/TestNugetPackageModule.cs +++ b/TUnit.Pipeline/Modules/TestNugetPackageModule.cs @@ -37,7 +37,7 @@ protected override IEnumerable TestableFrameworks { get { - yield return "net9.0"; + yield return "net10.0"; yield return "net8.0"; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt index 35bcf00a2e..e1e5f6bbf0 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt @@ -21,6 +21,10 @@ namespace public static . That(string? value, [.("value")] string? expression = null) { } [.(1)] public static . That(.? value, [.("value")] string? expression = null) { } + [.(1)] + public static . That(<.?> func, [.("func")] string? expression = null) { } + [.(1)] + public static . That(<.<.?>> func, [.("func")] string? expression = null) { } public static . That(<.> func, [.("func")] string? expression = null) { } public static . That( func, [.("func")] string? expression = null) { } public static . That(. task, [.("task")] string? expression = null) { } @@ -388,9 +392,9 @@ namespace .Conditions public abstract class CollectionComparerBasedAssertion : . where TCollection : . { + protected .? Comparer; protected CollectionComparerBasedAssertion(. context) { } protected . GetComparer() { } - protected bool HasCustomComparer() { } protected void SetComparer(. comparer) { } } [.("Contains")] @@ -817,15 +821,14 @@ namespace .Conditions protected override string GetExpectation() { } public . Using(. comparer) { } } - [.("Collection equivalency uses structural comparison for complex objects, which requ" + - "ires reflection and is not compatible with AOT")] [.("IsEquivalentTo")] public class IsEquivalentToAssertion : . where TCollection : . { + [.("Collection equivalency uses structural comparison for complex objects, which requ" + + "ires reflection and is not compatible with AOT")] public IsEquivalentToAssertion(. context, . expected, . ordering = 0) { } public IsEquivalentToAssertion(. context, . expected, . comparer, . ordering = 0) { } - [.("AOT", "IL3050", Justification="Collection equivalency uses structural comparison which requires reflection")] protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } public . Using(. comparer) { } @@ -919,14 +922,14 @@ namespace .Conditions public . IgnoringType( type) { } public . IgnoringType() { } } - [.("Collection equivalency uses structural comparison for complex objects, which requ" + - "ires reflection and is not compatible with AOT")] [.("IsNotEquivalentTo")] public class NotEquivalentToAssertion : . where TCollection : . { + [.("Collection equivalency uses structural comparison for complex objects, which requ" + + "ires reflection and is not compatible with AOT")] public NotEquivalentToAssertion(. context, . notExpected, . ordering = 0) { } - [.("AOT", "IL3050", Justification="Collection equivalency uses structural comparison which requires reflection")] + public NotEquivalentToAssertion(. context, . notExpected, . comparer, . ordering = 0) { } protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } public . Using(. comparer) { } @@ -1116,6 +1119,10 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class StringLengthValueAssertion : . + { + public StringLengthValueAssertion(. stringContext) { } + } public class StringMatchesAssertion : .<..RegexMatchCollection> { public StringMatchesAssertion(. context, . regex) { } @@ -1579,7 +1586,10 @@ namespace .Extensions public static . EqualTo(this . source, TValue? expected, [.("expected")] string? expression = null) { } public static ..HasFlagAssertion HasFlag(this . source, TEnum expectedFlag, [.("expectedFlag")] string? expression = null) where TEnum : struct, { } + [("Use Length() instead, which provides all numeric assertion methods. Example: Asse" + + "(str).Length().IsGreaterThan(5)")] public static ..LengthWrapper HasLength(this . source) { } + [("Use Length().IsEqualTo(expectedLength) instead.")] public static . HasLength(this . source, int expectedLength, [.("expectedLength")] string? expression = null) { } public static . HasMessageContaining(this . source, string expectedSubstring, [.("expectedSubstring")] string? expression = null) where TException : { } @@ -1634,6 +1644,7 @@ namespace .Extensions where TValue : { } public static . IsPositive(this . source) where TValue : struct, { } + public static . Length(this . source) { } [.(1)] public static . Member(this . source, .<>> memberSelector, <.<., TItem>, .<.>> assertions) { } [.("Uses reflection for legacy compatibility. For AOT compatibility, use the Member IsEquivalentTo(this . source, . expected, . ordering = 0, [.("expected")] string? expectedExpression = null, [.("ordering")] string? orderingExpression = null) where TCollection : . { } - [.("Collection equivalency uses structural comparison for complex objects, which requ" + - "ires reflection and is not compatible with AOT")] public static . IsEquivalentTo(this . source, . expected, . comparer, . ordering = 0, [.("expected")] string? expectedExpression = null, [.("comparer")] string? comparerExpression = null, [.("ordering")] string? orderingExpression = null) where TCollection : . { } } @@ -3184,6 +3193,8 @@ namespace .Extensions "ires reflection and is not compatible with AOT")] public static . IsNotEquivalentTo(this . source, . notExpected, . ordering = 0, [.("notExpected")] string? notExpectedExpression = null, [.("ordering")] string? orderingExpression = null) where TCollection : . { } + public static . IsNotEquivalentTo(this . source, . notExpected, . comparer, . ordering = 0, [.("notExpected")] string? notExpectedExpression = null, [.("comparer")] string? comparerExpression = null, [.("ordering")] string? orderingExpression = null) + where TCollection : . { } } public static class NotSameReferenceAssertionExtensions { @@ -4334,6 +4345,14 @@ namespace .Sources public . ThrowsExactly() where TException : { } } + public class AsyncFuncCollectionAssertion : .<., TItem>, ., .<.>, .<.> + { + public AsyncFuncCollectionAssertion(<.<.?>> func, string? expression) { } + public . Throws() + where TException : { } + public . ThrowsExactly() + where TException : { } + } public abstract class CollectionAssertionBase : ., ., . where TCollection : . { @@ -4351,7 +4370,10 @@ namespace .Sources public . DoesNotContain( predicate, [.("predicate")] string? expression = null) { } public . DoesNotContain(TItem expected, [.("expected")] string? expression = null) { } protected override string GetExpectation() { } + [("Use Count() instead, which provides all numeric assertion methods. Example: Asser" + + "(list).Count().IsGreaterThan(5)")] public ..CountWrapper HasCount() { } + [("Use Count().IsEqualTo(expectedCount) instead.")] public . HasCount(int expectedCount, [.("expectedCount")] string? expression = null) { } public . HasDistinctItems() { } public . HasSingleItem() { } @@ -4413,6 +4435,14 @@ namespace .Sources public . ThrowsExactly() where TException : { } } + public class FuncCollectionAssertion : .<., TItem>, ., .<.>, .<.> + { + public FuncCollectionAssertion(<.?> func, string? expression) { } + public . Throws() + where TException : { } + public . ThrowsExactly() + where TException : { } + } public class TaskAssertion : ., .<.>, ., . { public TaskAssertion(. task, string? expression) { } diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt index 060668995b..d5b2de52a2 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt @@ -19,6 +19,8 @@ namespace public static . That(. task, [.("task")] string? expression = null) { } public static . That(string? value, [.("value")] string? expression = null) { } public static . That(.? value, [.("value")] string? expression = null) { } + public static . That(<.?> func, [.("func")] string? expression = null) { } + public static . That(<.<.?>> func, [.("func")] string? expression = null) { } public static . That(<.> func, [.("func")] string? expression = null) { } public static . That( func, [.("func")] string? expression = null) { } public static . That(. task, [.("task")] string? expression = null) { } @@ -385,9 +387,9 @@ namespace .Conditions public abstract class CollectionComparerBasedAssertion : . where TCollection : . { + protected .? Comparer; protected CollectionComparerBasedAssertion(. context) { } protected . GetComparer() { } - protected bool HasCustomComparer() { } protected void SetComparer(. comparer) { } } [.("Contains")] @@ -814,15 +816,14 @@ namespace .Conditions protected override string GetExpectation() { } public . Using(. comparer) { } } - [.("Collection equivalency uses structural comparison for complex objects, which requ" + - "ires reflection and is not compatible with AOT")] [.("IsEquivalentTo")] public class IsEquivalentToAssertion : . where TCollection : . { + [.("Collection equivalency uses structural comparison for complex objects, which requ" + + "ires reflection and is not compatible with AOT")] public IsEquivalentToAssertion(. context, . expected, . ordering = 0) { } public IsEquivalentToAssertion(. context, . expected, . comparer, . ordering = 0) { } - [.("AOT", "IL3050", Justification="Collection equivalency uses structural comparison which requires reflection")] protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } public . Using(. comparer) { } @@ -916,14 +917,14 @@ namespace .Conditions public . IgnoringType( type) { } public . IgnoringType() { } } - [.("Collection equivalency uses structural comparison for complex objects, which requ" + - "ires reflection and is not compatible with AOT")] [.("IsNotEquivalentTo")] public class NotEquivalentToAssertion : . where TCollection : . { + [.("Collection equivalency uses structural comparison for complex objects, which requ" + + "ires reflection and is not compatible with AOT")] public NotEquivalentToAssertion(. context, . notExpected, . ordering = 0) { } - [.("AOT", "IL3050", Justification="Collection equivalency uses structural comparison which requires reflection")] + public NotEquivalentToAssertion(. context, . notExpected, . comparer, . ordering = 0) { } protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } public . Using(. comparer) { } @@ -1113,6 +1114,10 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class StringLengthValueAssertion : . + { + public StringLengthValueAssertion(. stringContext) { } + } public class StringMatchesAssertion : .<..RegexMatchCollection> { public StringMatchesAssertion(. context, . regex) { } @@ -1576,7 +1581,10 @@ namespace .Extensions public static . EqualTo(this . source, TValue? expected, [.("expected")] string? expression = null) { } public static ..HasFlagAssertion HasFlag(this . source, TEnum expectedFlag, [.("expectedFlag")] string? expression = null) where TEnum : struct, { } + [("Use Length() instead, which provides all numeric assertion methods. Example: Asse" + + "(str).Length().IsGreaterThan(5)")] public static ..LengthWrapper HasLength(this . source) { } + [("Use Length().IsEqualTo(expectedLength) instead.")] public static . HasLength(this . source, int expectedLength, [.("expectedLength")] string? expression = null) { } public static . HasMessageContaining(this . source, string expectedSubstring, [.("expectedSubstring")] string? expression = null) where TException : { } @@ -1631,6 +1639,7 @@ namespace .Extensions where TValue : { } public static . IsPositive(this . source) where TValue : struct, { } + public static . Length(this . source) { } public static . Member(this . source, .<>> memberSelector, <.<., TItem>, .<.>> assertions) { } [.("Uses reflection for legacy compatibility. For AOT compatibility, use the Member overload with strongly-typed assertions.")] @@ -3068,8 +3077,6 @@ namespace .Extensions "ires reflection and is not compatible with AOT")] public static . IsEquivalentTo(this . source, . expected, . ordering = 0, [.("expected")] string? expectedExpression = null, [.("ordering")] string? orderingExpression = null) where TCollection : . { } - [.("Collection equivalency uses structural comparison for complex objects, which requ" + - "ires reflection and is not compatible with AOT")] public static . IsEquivalentTo(this . source, . expected, . comparer, . ordering = 0, [.("expected")] string? expectedExpression = null, [.("comparer")] string? comparerExpression = null, [.("ordering")] string? orderingExpression = null) where TCollection : . { } } @@ -3166,6 +3173,8 @@ namespace .Extensions "ires reflection and is not compatible with AOT")] public static . IsNotEquivalentTo(this . source, . notExpected, . ordering = 0, [.("notExpected")] string? notExpectedExpression = null, [.("ordering")] string? orderingExpression = null) where TCollection : . { } + public static . IsNotEquivalentTo(this . source, . notExpected, . comparer, . ordering = 0, [.("notExpected")] string? notExpectedExpression = null, [.("comparer")] string? comparerExpression = null, [.("ordering")] string? orderingExpression = null) + where TCollection : . { } } public static class NotSameReferenceAssertionExtensions { @@ -4314,6 +4323,14 @@ namespace .Sources public . ThrowsExactly() where TException : { } } + public class AsyncFuncCollectionAssertion : .<., TItem>, ., .<.>, .<.> + { + public AsyncFuncCollectionAssertion(<.<.?>> func, string? expression) { } + public . Throws() + where TException : { } + public . ThrowsExactly() + where TException : { } + } public abstract class CollectionAssertionBase : ., ., . where TCollection : . { @@ -4331,7 +4348,10 @@ namespace .Sources public . DoesNotContain( predicate, [.("predicate")] string? expression = null) { } public . DoesNotContain(TItem expected, [.("expected")] string? expression = null) { } protected override string GetExpectation() { } + [("Use Count() instead, which provides all numeric assertion methods. Example: Asser" + + "(list).Count().IsGreaterThan(5)")] public ..CountWrapper HasCount() { } + [("Use Count().IsEqualTo(expectedCount) instead.")] public . HasCount(int expectedCount, [.("expectedCount")] string? expression = null) { } public . HasDistinctItems() { } public . HasSingleItem() { } @@ -4393,6 +4413,14 @@ namespace .Sources public . ThrowsExactly() where TException : { } } + public class FuncCollectionAssertion : .<., TItem>, ., .<.>, .<.> + { + public FuncCollectionAssertion(<.?> func, string? expression) { } + public . Throws() + where TException : { } + public . ThrowsExactly() + where TException : { } + } public class TaskAssertion : ., .<.>, ., . { public TaskAssertion(. task, string? expression) { } diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt index 634cbf0e26..a6c9c27621 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt @@ -21,6 +21,10 @@ namespace public static . That(string? value, [.("value")] string? expression = null) { } [.(1)] public static . That(.? value, [.("value")] string? expression = null) { } + [.(1)] + public static . That(<.?> func, [.("func")] string? expression = null) { } + [.(1)] + public static . That(<.<.?>> func, [.("func")] string? expression = null) { } public static . That(<.> func, [.("func")] string? expression = null) { } public static . That( func, [.("func")] string? expression = null) { } public static . That(. task, [.("task")] string? expression = null) { } @@ -388,9 +392,9 @@ namespace .Conditions public abstract class CollectionComparerBasedAssertion : . where TCollection : . { + protected .? Comparer; protected CollectionComparerBasedAssertion(. context) { } protected . GetComparer() { } - protected bool HasCustomComparer() { } protected void SetComparer(. comparer) { } } [.("Contains")] @@ -817,15 +821,14 @@ namespace .Conditions protected override string GetExpectation() { } public . Using(. comparer) { } } - [.("Collection equivalency uses structural comparison for complex objects, which requ" + - "ires reflection and is not compatible with AOT")] [.("IsEquivalentTo")] public class IsEquivalentToAssertion : . where TCollection : . { + [.("Collection equivalency uses structural comparison for complex objects, which requ" + + "ires reflection and is not compatible with AOT")] public IsEquivalentToAssertion(. context, . expected, . ordering = 0) { } public IsEquivalentToAssertion(. context, . expected, . comparer, . ordering = 0) { } - [.("AOT", "IL3050", Justification="Collection equivalency uses structural comparison which requires reflection")] protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } public . Using(. comparer) { } @@ -919,14 +922,14 @@ namespace .Conditions public . IgnoringType( type) { } public . IgnoringType() { } } - [.("Collection equivalency uses structural comparison for complex objects, which requ" + - "ires reflection and is not compatible with AOT")] [.("IsNotEquivalentTo")] public class NotEquivalentToAssertion : . where TCollection : . { + [.("Collection equivalency uses structural comparison for complex objects, which requ" + + "ires reflection and is not compatible with AOT")] public NotEquivalentToAssertion(. context, . notExpected, . ordering = 0) { } - [.("AOT", "IL3050", Justification="Collection equivalency uses structural comparison which requires reflection")] + public NotEquivalentToAssertion(. context, . notExpected, . comparer, . ordering = 0) { } protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } public . Using(. comparer) { } @@ -1116,6 +1119,10 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class StringLengthValueAssertion : . + { + public StringLengthValueAssertion(. stringContext) { } + } public class StringMatchesAssertion : .<..RegexMatchCollection> { public StringMatchesAssertion(. context, . regex) { } @@ -1579,7 +1586,10 @@ namespace .Extensions public static . EqualTo(this . source, TValue? expected, [.("expected")] string? expression = null) { } public static ..HasFlagAssertion HasFlag(this . source, TEnum expectedFlag, [.("expectedFlag")] string? expression = null) where TEnum : struct, { } + [("Use Length() instead, which provides all numeric assertion methods. Example: Asse" + + "(str).Length().IsGreaterThan(5)")] public static ..LengthWrapper HasLength(this . source) { } + [("Use Length().IsEqualTo(expectedLength) instead.")] public static . HasLength(this . source, int expectedLength, [.("expectedLength")] string? expression = null) { } public static . HasMessageContaining(this . source, string expectedSubstring, [.("expectedSubstring")] string? expression = null) where TException : { } @@ -1634,6 +1644,7 @@ namespace .Extensions where TValue : { } public static . IsPositive(this . source) where TValue : struct, { } + public static . Length(this . source) { } [.(1)] public static . Member(this . source, .<>> memberSelector, <.<., TItem>, .<.>> assertions) { } [.("Uses reflection for legacy compatibility. For AOT compatibility, use the Member IsEquivalentTo(this . source, . expected, . ordering = 0, [.("expected")] string? expectedExpression = null, [.("ordering")] string? orderingExpression = null) where TCollection : . { } - [.("Collection equivalency uses structural comparison for complex objects, which requ" + - "ires reflection and is not compatible with AOT")] public static . IsEquivalentTo(this . source, . expected, . comparer, . ordering = 0, [.("expected")] string? expectedExpression = null, [.("comparer")] string? comparerExpression = null, [.("ordering")] string? orderingExpression = null) where TCollection : . { } } @@ -3184,6 +3193,8 @@ namespace .Extensions "ires reflection and is not compatible with AOT")] public static . IsNotEquivalentTo(this . source, . notExpected, . ordering = 0, [.("notExpected")] string? notExpectedExpression = null, [.("ordering")] string? orderingExpression = null) where TCollection : . { } + public static . IsNotEquivalentTo(this . source, . notExpected, . comparer, . ordering = 0, [.("notExpected")] string? notExpectedExpression = null, [.("comparer")] string? comparerExpression = null, [.("ordering")] string? orderingExpression = null) + where TCollection : . { } } public static class NotSameReferenceAssertionExtensions { @@ -4334,6 +4345,14 @@ namespace .Sources public . ThrowsExactly() where TException : { } } + public class AsyncFuncCollectionAssertion : .<., TItem>, ., .<.>, .<.> + { + public AsyncFuncCollectionAssertion(<.<.?>> func, string? expression) { } + public . Throws() + where TException : { } + public . ThrowsExactly() + where TException : { } + } public abstract class CollectionAssertionBase : ., ., . where TCollection : . { @@ -4351,7 +4370,10 @@ namespace .Sources public . DoesNotContain( predicate, [.("predicate")] string? expression = null) { } public . DoesNotContain(TItem expected, [.("expected")] string? expression = null) { } protected override string GetExpectation() { } + [("Use Count() instead, which provides all numeric assertion methods. Example: Asser" + + "(list).Count().IsGreaterThan(5)")] public ..CountWrapper HasCount() { } + [("Use Count().IsEqualTo(expectedCount) instead.")] public . HasCount(int expectedCount, [.("expectedCount")] string? expression = null) { } public . HasDistinctItems() { } public . HasSingleItem() { } @@ -4413,6 +4435,14 @@ namespace .Sources public . ThrowsExactly() where TException : { } } + public class FuncCollectionAssertion : .<., TItem>, ., .<.>, .<.> + { + public FuncCollectionAssertion(<.?> func, string? expression) { } + public . Throws() + where TException : { } + public . ThrowsExactly() + where TException : { } + } public class TaskAssertion : ., .<.>, ., . { public TaskAssertion(. task, string? expression) { } diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt index 03c58549c0..6e579544ed 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt @@ -19,6 +19,8 @@ namespace public static . That(. task, [.("task")] string? expression = null) { } public static . That(string? value, [.("value")] string? expression = null) { } public static . That(.? value, [.("value")] string? expression = null) { } + public static . That(<.?> func, [.("func")] string? expression = null) { } + public static . That(<.<.?>> func, [.("func")] string? expression = null) { } public static . That(<.> func, [.("func")] string? expression = null) { } public static . That( func, [.("func")] string? expression = null) { } public static . That(. task, [.("task")] string? expression = null) { } @@ -381,9 +383,9 @@ namespace .Conditions public abstract class CollectionComparerBasedAssertion : . where TCollection : . { + protected .? Comparer; protected CollectionComparerBasedAssertion(. context) { } protected . GetComparer() { } - protected bool HasCustomComparer() { } protected void SetComparer(. comparer) { } } [.("Contains")] @@ -893,6 +895,7 @@ namespace .Conditions where TCollection : . { public NotEquivalentToAssertion(. context, . notExpected, . ordering = 0) { } + public NotEquivalentToAssertion(. context, . notExpected, . comparer, . ordering = 0) { } protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } public . Using(. comparer) { } @@ -1043,6 +1046,10 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class StringLengthValueAssertion : . + { + public StringLengthValueAssertion(. stringContext) { } + } public class StringMatchesAssertion : .<..RegexMatchCollection> { public StringMatchesAssertion(. context, . regex) { } @@ -1475,7 +1482,10 @@ namespace .Extensions public static . EqualTo(this . source, TValue? expected, [.("expected")] string? expression = null) { } public static ..HasFlagAssertion HasFlag(this . source, TEnum expectedFlag, [.("expectedFlag")] string? expression = null) where TEnum : struct, { } + [("Use Length() instead, which provides all numeric assertion methods. Example: Asse" + + "(str).Length().IsGreaterThan(5)")] public static ..LengthWrapper HasLength(this . source) { } + [("Use Length().IsEqualTo(expectedLength) instead.")] public static . HasLength(this . source, int expectedLength, [.("expectedLength")] string? expression = null) { } public static . HasMessageContaining(this . source, string expectedSubstring, [.("expectedSubstring")] string? expression = null) where TException : { } @@ -1520,6 +1530,7 @@ namespace .Extensions where TValue : { } public static . IsPositive(this . source) where TValue : struct, { } + public static . Length(this . source) { } public static . Member(this . source, .<>> memberSelector, <.<., TItem>, .<.>> assertions) { } public static . Member(this . source, .<>> memberSelector, <.<., TItem>, object> assertions) { } public static . Member(this . source, .<> memberSelector, <., .> assertions) { } @@ -2881,6 +2892,8 @@ namespace .Extensions { public static . IsNotEquivalentTo(this . source, . notExpected, . ordering = 0, [.("notExpected")] string? notExpectedExpression = null, [.("ordering")] string? orderingExpression = null) where TCollection : . { } + public static . IsNotEquivalentTo(this . source, . notExpected, . comparer, . ordering = 0, [.("notExpected")] string? notExpectedExpression = null, [.("comparer")] string? comparerExpression = null, [.("ordering")] string? orderingExpression = null) + where TCollection : . { } } public static class NotSameReferenceAssertionExtensions { @@ -3799,6 +3812,14 @@ namespace .Sources public . ThrowsExactly() where TException : { } } + public class AsyncFuncCollectionAssertion : .<., TItem>, ., .<.>, .<.> + { + public AsyncFuncCollectionAssertion(<.<.?>> func, string? expression) { } + public . Throws() + where TException : { } + public . ThrowsExactly() + where TException : { } + } public abstract class CollectionAssertionBase : ., ., . where TCollection : . { @@ -3816,7 +3837,10 @@ namespace .Sources public . DoesNotContain( predicate, [.("predicate")] string? expression = null) { } public . DoesNotContain(TItem expected, [.("expected")] string? expression = null) { } protected override string GetExpectation() { } + [("Use Count() instead, which provides all numeric assertion methods. Example: Asser" + + "(list).Count().IsGreaterThan(5)")] public ..CountWrapper HasCount() { } + [("Use Count().IsEqualTo(expectedCount) instead.")] public . HasCount(int expectedCount, [.("expectedCount")] string? expression = null) { } public . HasDistinctItems() { } public . HasSingleItem() { } @@ -3878,6 +3902,14 @@ namespace .Sources public . ThrowsExactly() where TException : { } } + public class FuncCollectionAssertion : .<., TItem>, ., .<.>, .<.> + { + public FuncCollectionAssertion(<.?> func, string? expression) { } + public . Throws() + where TException : { } + public . ThrowsExactly() + where TException : { } + } public class TaskAssertion : ., .<.>, ., . { public TaskAssertion(. task, string? expression) { } diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt index dd8b8234d0..a01bfb6b84 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt @@ -371,7 +371,7 @@ namespace public static .ClassMetadata GetOrAdd(string name, <.ClassMetadata> factory) { } } [(.Class | .Method)] - public sealed class CombinedDataSourcesAttribute : .AsyncUntypedDataSourceGeneratorAttribute, .IAccessesInstanceData + public sealed class CombinedDataSourcesAttribute : .AsyncUntypedDataSourceGeneratorAttribute { public CombinedDataSourcesAttribute() { } [.(typeof(.CombinedDataSourcesAttribute.d__0))] @@ -399,8 +399,8 @@ namespace public void AddAsyncLocalValues() { } public void Dispose() { } public . GetDefaultLogger() { } - public string GetErrorOutput() { } - public string GetStandardOutput() { } + public virtual string GetErrorOutput() { } + public virtual string GetStandardOutput() { } public void RestoreExecutionContext() { } } public class ContextProvider : . @@ -881,6 +881,7 @@ namespace public class InstanceMethodDataSourceAttribute : .MethodDataSourceAttribute, .IAccessesInstanceData { public InstanceMethodDataSourceAttribute(string methodNameProvidingDataSource) { } + public InstanceMethodDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] classProvidingDataSource, string methodNameProvidingDataSource) { } } public class InvalidTestMetadataException : .TestBuilderException { @@ -999,10 +1000,6 @@ namespace public . NotInParallelConstraintKeys { get; init; } public int Order { get; set; } } - public static class ObjectInitializer - { - public static . InitializeAsync(object? obj, .CancellationToken cancellationToken = default) { } - } public class ParallelGroupAttribute : .TUnitAttribute, ., . { public ParallelGroupAttribute(string group) { } @@ -1241,6 +1238,14 @@ namespace { public TestAttribute([.] string file = "", [.] int line = 0) { } } + public sealed class TestBuildContext : .Context, + { + public TestBuildContext() { } + public new static .TestBuildContext? Current { get; } + public new void Dispose() { } + public string GetCapturedErrorOutput() { } + public string GetCapturedOutput() { } + } public class TestBuilderContext : <.TestBuilderContext> { public TestBuilderContext() { } @@ -1291,6 +1296,8 @@ namespace public static string? OutputDirectory { get; } public static .> Parameters { get; } public static string WorkingDirectory { get; set; } + public override string GetErrorOutput() { } + public override string GetStandardOutput() { } public static .TestContext? GetById(string id) { } } public class TestContextEvents : . @@ -1665,6 +1672,11 @@ namespace .Enums TestParameters = 1, Property = 2, } + public enum EventReceiverStage + { + Early = 0, + Late = 1, + } public enum LogLevel { None = -1, @@ -2005,14 +2017,23 @@ namespace .Helpers [.("MakeGenericType requires runtime code generation")] public static MakeGenericTypeSafe( genericTypeDefinition, params [] typeArguments) { } } + public static class ParallelTaskHelper + { + public static . ForEachAsync(. items, action) { } + public static . ForEachAsync(T[] items, action) { } + public static . ForEachAsync(. items, action, .CancellationToken cancellationToken) { } + public static . ForEachAsync(T[] items, action, .CancellationToken cancellationToken) { } + public static . ForEachWithIndexAsync(T[] items, action) { } + public static . ForEachWithIndexAsync(T[] items, action, .CancellationToken cancellationToken) { } + } public class ProcessorCountParallelLimit : . { public ProcessorCountParallelLimit() { } public int Limit { get; } } - public class ReferenceEqualityComparer : . + public sealed class ReferenceEqualityComparer : . { - public ReferenceEqualityComparer() { } + public static readonly . Instance; public bool Equals(object? x, object? y) { } public int GetHashCode(object obj) { } } @@ -2183,6 +2204,7 @@ namespace .Interfaces { .<> GenerateDataFactoriesAsync(.DataSourceContext context, .CancellationToken cancellationToken = default); } + public interface IAsyncDiscoveryInitializer : . { } public interface IAsyncInitializer { . InitializeAsync(); @@ -2212,6 +2234,10 @@ namespace .Interfaces { .<> GenerateDataFactories(.DataSourceContext context); } + public interface IDisposer + { + . DisposeAsync(object? obj); + } public interface IEventReceiver { int Order { get; } @@ -2328,6 +2354,7 @@ namespace .Interfaces } public interface ITestEndEventReceiver : . { + . Stage { get; } . OnTestEnd(.TestContext context); } public interface ITestEvents @@ -2443,6 +2470,7 @@ namespace .Interfaces } public interface ITestStartEventReceiver : . { + . Stage { get; } . OnTestStart(.TestContext context); } public interface ITestStateBag @@ -2476,7 +2504,7 @@ namespace . public sealed class PropertyInjectionMetadata { public PropertyInjectionMetadata() { } - [.(..PublicProperties)] + [.(..None | ..PublicProperties | ..NonPublicProperties)] public required ContainingType { get; init; } public required <.IDataSourceAttribute> CreateDataSource { get; init; } public required string PropertyName { get; init; } @@ -2569,6 +2597,14 @@ namespace .Models public ? MethodInvoker { get; set; } } } +namespace .PropertyInjection +{ + public static class PropertyCacheKeyGenerator + { + public static string GetCacheKey(.PropertyInfo property) { } + public static string GetCacheKey(..PropertyInjectionMetadata metadata) { } + } +} namespace .Services { [.("Generic type resolution requires runtime type generation")] diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt index 0f5261a9e5..6473654dd9 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt @@ -371,7 +371,7 @@ namespace public static .ClassMetadata GetOrAdd(string name, <.ClassMetadata> factory) { } } [(.Class | .Method)] - public sealed class CombinedDataSourcesAttribute : .AsyncUntypedDataSourceGeneratorAttribute, .IAccessesInstanceData + public sealed class CombinedDataSourcesAttribute : .AsyncUntypedDataSourceGeneratorAttribute { public CombinedDataSourcesAttribute() { } [.(typeof(.CombinedDataSourcesAttribute.d__0))] @@ -399,8 +399,8 @@ namespace public void AddAsyncLocalValues() { } public void Dispose() { } public . GetDefaultLogger() { } - public string GetErrorOutput() { } - public string GetStandardOutput() { } + public virtual string GetErrorOutput() { } + public virtual string GetStandardOutput() { } public void RestoreExecutionContext() { } } public class ContextProvider : . @@ -881,6 +881,7 @@ namespace public class InstanceMethodDataSourceAttribute : .MethodDataSourceAttribute, .IAccessesInstanceData { public InstanceMethodDataSourceAttribute(string methodNameProvidingDataSource) { } + public InstanceMethodDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] classProvidingDataSource, string methodNameProvidingDataSource) { } } public class InvalidTestMetadataException : .TestBuilderException { @@ -999,10 +1000,6 @@ namespace public . NotInParallelConstraintKeys { get; init; } public int Order { get; set; } } - public static class ObjectInitializer - { - public static . InitializeAsync(object? obj, .CancellationToken cancellationToken = default) { } - } public class ParallelGroupAttribute : .TUnitAttribute, ., . { public ParallelGroupAttribute(string group) { } @@ -1241,6 +1238,14 @@ namespace { public TestAttribute([.] string file = "", [.] int line = 0) { } } + public sealed class TestBuildContext : .Context, + { + public TestBuildContext() { } + public new static .TestBuildContext? Current { get; } + public new void Dispose() { } + public string GetCapturedErrorOutput() { } + public string GetCapturedOutput() { } + } public class TestBuilderContext : <.TestBuilderContext> { public TestBuilderContext() { } @@ -1291,6 +1296,8 @@ namespace public static string? OutputDirectory { get; } public static .> Parameters { get; } public static string WorkingDirectory { get; set; } + public override string GetErrorOutput() { } + public override string GetStandardOutput() { } public static .TestContext? GetById(string id) { } } public class TestContextEvents : . @@ -1665,6 +1672,11 @@ namespace .Enums TestParameters = 1, Property = 2, } + public enum EventReceiverStage + { + Early = 0, + Late = 1, + } public enum LogLevel { None = -1, @@ -2005,14 +2017,23 @@ namespace .Helpers [.("MakeGenericType requires runtime code generation")] public static MakeGenericTypeSafe( genericTypeDefinition, params [] typeArguments) { } } + public static class ParallelTaskHelper + { + public static . ForEachAsync(. items, action) { } + public static . ForEachAsync(T[] items, action) { } + public static . ForEachAsync(. items, action, .CancellationToken cancellationToken) { } + public static . ForEachAsync(T[] items, action, .CancellationToken cancellationToken) { } + public static . ForEachWithIndexAsync(T[] items, action) { } + public static . ForEachWithIndexAsync(T[] items, action, .CancellationToken cancellationToken) { } + } public class ProcessorCountParallelLimit : . { public ProcessorCountParallelLimit() { } public int Limit { get; } } - public class ReferenceEqualityComparer : . + public sealed class ReferenceEqualityComparer : . { - public ReferenceEqualityComparer() { } + public static readonly . Instance; public bool Equals(object? x, object? y) { } public int GetHashCode(object obj) { } } @@ -2183,6 +2204,7 @@ namespace .Interfaces { .<> GenerateDataFactoriesAsync(.DataSourceContext context, .CancellationToken cancellationToken = default); } + public interface IAsyncDiscoveryInitializer : . { } public interface IAsyncInitializer { . InitializeAsync(); @@ -2212,6 +2234,10 @@ namespace .Interfaces { .<> GenerateDataFactories(.DataSourceContext context); } + public interface IDisposer + { + . DisposeAsync(object? obj); + } public interface IEventReceiver { int Order { get; } @@ -2328,6 +2354,7 @@ namespace .Interfaces } public interface ITestEndEventReceiver : . { + . Stage { get; } . OnTestEnd(.TestContext context); } public interface ITestEvents @@ -2443,6 +2470,7 @@ namespace .Interfaces } public interface ITestStartEventReceiver : . { + . Stage { get; } . OnTestStart(.TestContext context); } public interface ITestStateBag @@ -2476,7 +2504,7 @@ namespace . public sealed class PropertyInjectionMetadata { public PropertyInjectionMetadata() { } - [.(..PublicProperties)] + [.(..None | ..PublicProperties | ..NonPublicProperties)] public required ContainingType { get; init; } public required <.IDataSourceAttribute> CreateDataSource { get; init; } public required string PropertyName { get; init; } @@ -2569,6 +2597,14 @@ namespace .Models public ? MethodInvoker { get; set; } } } +namespace .PropertyInjection +{ + public static class PropertyCacheKeyGenerator + { + public static string GetCacheKey(.PropertyInfo property) { } + public static string GetCacheKey(..PropertyInjectionMetadata metadata) { } + } +} namespace .Services { [.("Generic type resolution requires runtime type generation")] diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt index 46cb1ff278..f5e15d8132 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt @@ -371,7 +371,7 @@ namespace public static .ClassMetadata GetOrAdd(string name, <.ClassMetadata> factory) { } } [(.Class | .Method)] - public sealed class CombinedDataSourcesAttribute : .AsyncUntypedDataSourceGeneratorAttribute, .IAccessesInstanceData + public sealed class CombinedDataSourcesAttribute : .AsyncUntypedDataSourceGeneratorAttribute { public CombinedDataSourcesAttribute() { } [.(typeof(.CombinedDataSourcesAttribute.d__0))] @@ -399,8 +399,8 @@ namespace public void AddAsyncLocalValues() { } public void Dispose() { } public . GetDefaultLogger() { } - public string GetErrorOutput() { } - public string GetStandardOutput() { } + public virtual string GetErrorOutput() { } + public virtual string GetStandardOutput() { } public void RestoreExecutionContext() { } } public class ContextProvider : . @@ -881,6 +881,7 @@ namespace public class InstanceMethodDataSourceAttribute : .MethodDataSourceAttribute, .IAccessesInstanceData { public InstanceMethodDataSourceAttribute(string methodNameProvidingDataSource) { } + public InstanceMethodDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] classProvidingDataSource, string methodNameProvidingDataSource) { } } public class InvalidTestMetadataException : .TestBuilderException { @@ -999,10 +1000,6 @@ namespace public . NotInParallelConstraintKeys { get; init; } public int Order { get; set; } } - public static class ObjectInitializer - { - public static . InitializeAsync(object? obj, .CancellationToken cancellationToken = default) { } - } public class ParallelGroupAttribute : .TUnitAttribute, ., . { public ParallelGroupAttribute(string group) { } @@ -1241,6 +1238,14 @@ namespace { public TestAttribute([.] string file = "", [.] int line = 0) { } } + public sealed class TestBuildContext : .Context, + { + public TestBuildContext() { } + public new static .TestBuildContext? Current { get; } + public new void Dispose() { } + public string GetCapturedErrorOutput() { } + public string GetCapturedOutput() { } + } public class TestBuilderContext : <.TestBuilderContext> { public TestBuilderContext() { } @@ -1291,6 +1296,8 @@ namespace public static string? OutputDirectory { get; } public static .> Parameters { get; } public static string WorkingDirectory { get; set; } + public override string GetErrorOutput() { } + public override string GetStandardOutput() { } public static .TestContext? GetById(string id) { } } public class TestContextEvents : . @@ -1665,6 +1672,11 @@ namespace .Enums TestParameters = 1, Property = 2, } + public enum EventReceiverStage + { + Early = 0, + Late = 1, + } public enum LogLevel { None = -1, @@ -2005,14 +2017,23 @@ namespace .Helpers [.("MakeGenericType requires runtime code generation")] public static MakeGenericTypeSafe( genericTypeDefinition, params [] typeArguments) { } } + public static class ParallelTaskHelper + { + public static . ForEachAsync(. items, action) { } + public static . ForEachAsync(T[] items, action) { } + public static . ForEachAsync(. items, action, .CancellationToken cancellationToken) { } + public static . ForEachAsync(T[] items, action, .CancellationToken cancellationToken) { } + public static . ForEachWithIndexAsync(T[] items, action) { } + public static . ForEachWithIndexAsync(T[] items, action, .CancellationToken cancellationToken) { } + } public class ProcessorCountParallelLimit : . { public ProcessorCountParallelLimit() { } public int Limit { get; } } - public class ReferenceEqualityComparer : . + public sealed class ReferenceEqualityComparer : . { - public ReferenceEqualityComparer() { } + public static readonly . Instance; public bool Equals(object? x, object? y) { } public int GetHashCode(object obj) { } } @@ -2183,6 +2204,7 @@ namespace .Interfaces { .<> GenerateDataFactoriesAsync(.DataSourceContext context, .CancellationToken cancellationToken = default); } + public interface IAsyncDiscoveryInitializer : . { } public interface IAsyncInitializer { . InitializeAsync(); @@ -2212,6 +2234,10 @@ namespace .Interfaces { .<> GenerateDataFactories(.DataSourceContext context); } + public interface IDisposer + { + . DisposeAsync(object? obj); + } public interface IEventReceiver { int Order { get; } @@ -2328,6 +2354,7 @@ namespace .Interfaces } public interface ITestEndEventReceiver : . { + . Stage { get; } . OnTestEnd(.TestContext context); } public interface ITestEvents @@ -2443,6 +2470,7 @@ namespace .Interfaces } public interface ITestStartEventReceiver : . { + . Stage { get; } . OnTestStart(.TestContext context); } public interface ITestStateBag @@ -2476,7 +2504,7 @@ namespace . public sealed class PropertyInjectionMetadata { public PropertyInjectionMetadata() { } - [.(..PublicProperties)] + [.(..None | ..PublicProperties | ..NonPublicProperties)] public required ContainingType { get; init; } public required <.IDataSourceAttribute> CreateDataSource { get; init; } public required string PropertyName { get; init; } @@ -2569,6 +2597,14 @@ namespace .Models public ? MethodInvoker { get; set; } } } +namespace .PropertyInjection +{ + public static class PropertyCacheKeyGenerator + { + public static string GetCacheKey(.PropertyInfo property) { } + public static string GetCacheKey(..PropertyInjectionMetadata metadata) { } + } +} namespace .Services { [.("Generic type resolution requires runtime type generation")] diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt index 2f4897b17a..143ce52b60 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt @@ -351,7 +351,7 @@ namespace public static .ClassMetadata GetOrAdd(string name, <.ClassMetadata> factory) { } } [(.Class | .Method)] - public sealed class CombinedDataSourcesAttribute : .AsyncUntypedDataSourceGeneratorAttribute, .IAccessesInstanceData + public sealed class CombinedDataSourcesAttribute : .AsyncUntypedDataSourceGeneratorAttribute { public CombinedDataSourcesAttribute() { } [.(typeof(.CombinedDataSourcesAttribute.d__0))] @@ -379,8 +379,8 @@ namespace public void AddAsyncLocalValues() { } public void Dispose() { } public . GetDefaultLogger() { } - public string GetErrorOutput() { } - public string GetStandardOutput() { } + public virtual string GetErrorOutput() { } + public virtual string GetStandardOutput() { } public void RestoreExecutionContext() { } } public class ContextProvider : . @@ -858,6 +858,7 @@ namespace public class InstanceMethodDataSourceAttribute : .MethodDataSourceAttribute, .IAccessesInstanceData { public InstanceMethodDataSourceAttribute(string methodNameProvidingDataSource) { } + public InstanceMethodDataSourceAttribute( classProvidingDataSource, string methodNameProvidingDataSource) { } } public class InvalidTestMetadataException : .TestBuilderException { @@ -962,10 +963,6 @@ namespace public . NotInParallelConstraintKeys { get; init; } public int Order { get; set; } } - public static class ObjectInitializer - { - public static . InitializeAsync(object? obj, .CancellationToken cancellationToken = default) { } - } public class ParallelGroupAttribute : .TUnitAttribute, ., . { public ParallelGroupAttribute(string group) { } @@ -1196,6 +1193,14 @@ namespace { public TestAttribute([.] string file = "", [.] int line = 0) { } } + public sealed class TestBuildContext : .Context, + { + public TestBuildContext() { } + public new static .TestBuildContext? Current { get; } + public new void Dispose() { } + public string GetCapturedErrorOutput() { } + public string GetCapturedOutput() { } + } public class TestBuilderContext : <.TestBuilderContext> { public TestBuilderContext() { } @@ -1246,6 +1251,8 @@ namespace public static string? OutputDirectory { get; } public static .> Parameters { get; } public static string WorkingDirectory { get; set; } + public override string GetErrorOutput() { } + public override string GetStandardOutput() { } public static .TestContext? GetById(string id) { } } public class TestContextEvents : . @@ -1618,6 +1625,11 @@ namespace .Enums TestParameters = 1, Property = 2, } + public enum EventReceiverStage + { + Early = 0, + Late = 1, + } public enum LogLevel { None = -1, @@ -1944,14 +1956,23 @@ namespace .Helpers public static bool IsConstructedGenericType( type) { } public static MakeGenericTypeSafe( genericTypeDefinition, params [] typeArguments) { } } + public static class ParallelTaskHelper + { + public static . ForEachAsync(. items, action) { } + public static . ForEachAsync(T[] items, action) { } + public static . ForEachAsync(. items, action, .CancellationToken cancellationToken) { } + public static . ForEachAsync(T[] items, action, .CancellationToken cancellationToken) { } + public static . ForEachWithIndexAsync(T[] items, action) { } + public static . ForEachWithIndexAsync(T[] items, action, .CancellationToken cancellationToken) { } + } public class ProcessorCountParallelLimit : . { public ProcessorCountParallelLimit() { } public int Limit { get; } } - public class ReferenceEqualityComparer : . + public sealed class ReferenceEqualityComparer : . { - public ReferenceEqualityComparer() { } + public static readonly . Instance; public bool Equals(object? x, object? y) { } public int GetHashCode(object obj) { } } @@ -2115,6 +2136,7 @@ namespace .Interfaces { .<> GenerateDataFactoriesAsync(.DataSourceContext context, .CancellationToken cancellationToken = default); } + public interface IAsyncDiscoveryInitializer : . { } public interface IAsyncInitializer { . InitializeAsync(); @@ -2144,6 +2166,10 @@ namespace .Interfaces { .<> GenerateDataFactories(.DataSourceContext context); } + public interface IDisposer + { + . DisposeAsync(object? obj); + } public interface IEventReceiver { int Order { get; } @@ -2493,6 +2519,14 @@ namespace .Models public ? MethodInvoker { get; set; } } } +namespace .PropertyInjection +{ + public static class PropertyCacheKeyGenerator + { + public static string GetCacheKey(.PropertyInfo property) { } + public static string GetCacheKey(..PropertyInjectionMetadata metadata) { } + } +} namespace .Services { public class GenericTypeResolver : . diff --git a/TUnit.PublicAPI/Tests.cs b/TUnit.PublicAPI/Tests.cs index 0aa7f2dff9..67830f80f0 100644 --- a/TUnit.PublicAPI/Tests.cs +++ b/TUnit.PublicAPI/Tests.cs @@ -51,8 +51,8 @@ await VerifyTUnit.Verify(publicApi) .ScrubFilePaths() .OnVerifyMismatch(async (pair, message, verify) => { - var received = await FilePolyfill.ReadAllTextAsync(pair.ReceivedPath); - var verified = await FilePolyfill.ReadAllTextAsync(pair.VerifiedPath); + var received = await File.ReadAllTextAsync(pair.ReceivedPath); + var verified = await File.ReadAllTextAsync(pair.VerifiedPath); // Better diff message since original one is too large await Assert.That(Scrub(received)).IsEqualTo(Scrub(verified)); diff --git a/TUnit.PublicAPI/Verify.cs b/TUnit.PublicAPI/Verify.cs index d057db9a0b..5059fc603c 100644 --- a/TUnit.PublicAPI/Verify.cs +++ b/TUnit.PublicAPI/Verify.cs @@ -129,15 +129,15 @@ public async Task ToTask() if (!File.Exists(_verifiedPath)) { - await FilePolyfill.WriteAllTextAsync(_receivedPath, NormalizeNewline(final)); + await File.WriteAllTextAsync(_receivedPath, NormalizeNewline(final)); throw new InvalidOperationException($"No verified file found for '{name}'."); } - var approved = await FilePolyfill.ReadAllTextAsync(_verifiedPath); + var approved = await File.ReadAllTextAsync(_verifiedPath); if (!string.Equals(NormalizeNewline(final), NormalizeNewline(approved), StringComparison.Ordinal)) { - await FilePolyfill.WriteAllTextAsync(_receivedPath, NormalizeNewline(final)); + await File.WriteAllTextAsync(_receivedPath, NormalizeNewline(final)); if (_onVerifyMismatch != null) { diff --git a/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.AspNet._.verified/TUnit.AspNet/TUnit.AspNet/TUnit.AspNet.csproj b/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.AspNet._.verified/TUnit.AspNet/TUnit.AspNet/TUnit.AspNet.csproj index 8967721da8..eb10888b9e 100644 --- a/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.AspNet._.verified/TUnit.AspNet/TUnit.AspNet/TUnit.AspNet.csproj +++ b/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.AspNet._.verified/TUnit.AspNet/TUnit.AspNet/TUnit.AspNet.csproj @@ -4,7 +4,7 @@ enable enable Exe - net9.0 + net10.0 diff --git a/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.AspNet._.verified/TUnit.AspNet/WebApp/WebApp.csproj b/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.AspNet._.verified/TUnit.AspNet/WebApp/WebApp.csproj index 0fb72c4b20..a6a96dcf18 100644 --- a/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.AspNet._.verified/TUnit.AspNet/WebApp/WebApp.csproj +++ b/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.AspNet._.verified/TUnit.AspNet/WebApp/WebApp.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable diff --git a/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.ApiService/TUnit.Aspire.Starter.ApiService.csproj b/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.ApiService/TUnit.Aspire.Starter.ApiService.csproj index 0cdcf8be70..1ad71b0e28 100644 --- a/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.ApiService/TUnit.Aspire.Starter.ApiService.csproj +++ b/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.ApiService/TUnit.Aspire.Starter.ApiService.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable diff --git a/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.AppHost/TUnit.Aspire.Starter.AppHost.csproj b/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.AppHost/TUnit.Aspire.Starter.AppHost.csproj index 18012ac870..3cbe117a5a 100644 --- a/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.AppHost/TUnit.Aspire.Starter.AppHost.csproj +++ b/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.AppHost/TUnit.Aspire.Starter.AppHost.csproj @@ -4,7 +4,7 @@ Exe - net9.0 + net10.0 enable enable true diff --git a/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.ServiceDefaults/TUnit.Aspire.Starter.ServiceDefaults.csproj b/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.ServiceDefaults/TUnit.Aspire.Starter.ServiceDefaults.csproj index 6b09c19d12..f0593381b8 100644 --- a/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.ServiceDefaults/TUnit.Aspire.Starter.ServiceDefaults.csproj +++ b/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.ServiceDefaults/TUnit.Aspire.Starter.ServiceDefaults.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable true diff --git a/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.TestProject/TUnit.Aspire.Starter.TestProject.csproj b/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.TestProject/TUnit.Aspire.Starter.TestProject.csproj index 2356fbfa25..241b9eb367 100644 --- a/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.TestProject/TUnit.Aspire.Starter.TestProject.csproj +++ b/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.TestProject/TUnit.Aspire.Starter.TestProject.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable false diff --git a/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.WebApp/TUnit.Aspire.Starter.WebApp.csproj b/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.WebApp/TUnit.Aspire.Starter.WebApp.csproj index ddb4c3bf05..d871db6526 100644 --- a/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.WebApp/TUnit.Aspire.Starter.WebApp.csproj +++ b/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Starter._.verified/TUnit.Aspire.Starter/TUnit.Aspire.Starter.WebApp/TUnit.Aspire.Starter.WebApp.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable diff --git a/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Test._.verified/TUnit.Aspire.Test/TUnit.Aspire.Test.csproj b/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Test._.verified/TUnit.Aspire.Test/TUnit.Aspire.Test.csproj index 3606790ad9..3646299f7b 100644 --- a/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Test._.verified/TUnit.Aspire.Test/TUnit.Aspire.Test.csproj +++ b/TUnit.Templates.Tests/Snapshots/InstantiationTest.TUnit.Aspire.Test._.verified/TUnit.Aspire.Test/TUnit.Aspire.Test.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable false diff --git a/TUnit.Templates.Tests/Snapshots/InstantiationTestWithFSharp.TUnit.AspNet.FSharp._.verified/TUnit.AspNet.FSharp/TUnit.AspNet.FSharp/TUnit.AspNet.FSharp.fsproj b/TUnit.Templates.Tests/Snapshots/InstantiationTestWithFSharp.TUnit.AspNet.FSharp._.verified/TUnit.AspNet.FSharp/TUnit.AspNet.FSharp/TUnit.AspNet.FSharp.fsproj index be7d8a2c0d..30cbfc590b 100644 --- a/TUnit.Templates.Tests/Snapshots/InstantiationTestWithFSharp.TUnit.AspNet.FSharp._.verified/TUnit.AspNet.FSharp/TUnit.AspNet.FSharp/TUnit.AspNet.FSharp.fsproj +++ b/TUnit.Templates.Tests/Snapshots/InstantiationTestWithFSharp.TUnit.AspNet.FSharp._.verified/TUnit.AspNet.FSharp/TUnit.AspNet.FSharp/TUnit.AspNet.FSharp.fsproj @@ -5,7 +5,7 @@ enable enable Exe - net9.0 + net10.0 diff --git a/TUnit.Templates.Tests/Snapshots/InstantiationTestWithFSharp.TUnit.AspNet.FSharp._.verified/TUnit.AspNet.FSharp/WebApp/WebApp.fsproj b/TUnit.Templates.Tests/Snapshots/InstantiationTestWithFSharp.TUnit.AspNet.FSharp._.verified/TUnit.AspNet.FSharp/WebApp/WebApp.fsproj index 4018f042a6..5393f5177d 100644 --- a/TUnit.Templates.Tests/Snapshots/InstantiationTestWithFSharp.TUnit.AspNet.FSharp._.verified/TUnit.AspNet.FSharp/WebApp/WebApp.fsproj +++ b/TUnit.Templates.Tests/Snapshots/InstantiationTestWithFSharp.TUnit.AspNet.FSharp._.verified/TUnit.AspNet.FSharp/WebApp/WebApp.fsproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 diff --git a/TUnit.Templates.Tests/TUnit.Templates.Tests.csproj b/TUnit.Templates.Tests/TUnit.Templates.Tests.csproj index bfa9211dda..2868a34ab1 100644 --- a/TUnit.Templates.Tests/TUnit.Templates.Tests.csproj +++ b/TUnit.Templates.Tests/TUnit.Templates.Tests.csproj @@ -3,7 +3,7 @@ - net9.0;net10.0 + net10.0 diff --git a/TUnit.Templates/TUnit.Templates.csproj b/TUnit.Templates/TUnit.Templates.csproj index f5fea84539..97c688cbf6 100644 --- a/TUnit.Templates/TUnit.Templates.csproj +++ b/TUnit.Templates/TUnit.Templates.csproj @@ -10,7 +10,7 @@ https://www.github.com/thomhurst/TUnit Template - net9.0 + net10.0 true false content diff --git a/TUnit.Templates/content/TUnit.AspNet.FSharp/TestProject/TestProject.fsproj b/TUnit.Templates/content/TUnit.AspNet.FSharp/TestProject/TestProject.fsproj index f1ba496a75..5de92f03b5 100644 --- a/TUnit.Templates/content/TUnit.AspNet.FSharp/TestProject/TestProject.fsproj +++ b/TUnit.Templates/content/TUnit.AspNet.FSharp/TestProject/TestProject.fsproj @@ -5,13 +5,13 @@ enable enable Exe - net9.0 + net10.0 - - - + + + diff --git a/TUnit.Templates/content/TUnit.AspNet.FSharp/WebApp/WebApp.fsproj b/TUnit.Templates/content/TUnit.AspNet.FSharp/WebApp/WebApp.fsproj index b42e1e3d59..e010c79135 100644 --- a/TUnit.Templates/content/TUnit.AspNet.FSharp/WebApp/WebApp.fsproj +++ b/TUnit.Templates/content/TUnit.AspNet.FSharp/WebApp/WebApp.fsproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 diff --git a/TUnit.Templates/content/TUnit.AspNet/TestProject/TestProject.csproj b/TUnit.Templates/content/TUnit.AspNet/TestProject/TestProject.csproj index 287051cd44..d2869e55bb 100644 --- a/TUnit.Templates/content/TUnit.AspNet/TestProject/TestProject.csproj +++ b/TUnit.Templates/content/TUnit.AspNet/TestProject/TestProject.csproj @@ -4,12 +4,12 @@ enable enable Exe - net9.0 + net10.0 - - + + diff --git a/TUnit.Templates/content/TUnit.AspNet/WebApp/WebApp.csproj b/TUnit.Templates/content/TUnit.AspNet/WebApp/WebApp.csproj index d77de3aded..810579733e 100644 --- a/TUnit.Templates/content/TUnit.AspNet/WebApp/WebApp.csproj +++ b/TUnit.Templates/content/TUnit.AspNet/WebApp/WebApp.csproj @@ -1,13 +1,13 @@  - net9.0 + net10.0 enable enable - + diff --git a/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.ApiService/ExampleNamespace.ApiService.csproj b/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.ApiService/ExampleNamespace.ApiService.csproj index b48bfef63c..ed704c50b0 100644 --- a/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.ApiService/ExampleNamespace.ApiService.csproj +++ b/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.ApiService/ExampleNamespace.ApiService.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable @@ -11,7 +11,7 @@ - + diff --git a/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.AppHost/ExampleNamespace.AppHost.csproj b/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.AppHost/ExampleNamespace.AppHost.csproj index a712d7c89c..dec89856dc 100644 --- a/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.AppHost/ExampleNamespace.AppHost.csproj +++ b/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.AppHost/ExampleNamespace.AppHost.csproj @@ -1,10 +1,10 @@  - + Exe - net9.0 + net10.0 enable enable true @@ -12,8 +12,8 @@ - - + + diff --git a/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.ServiceDefaults/ExampleNamespace.ServiceDefaults.csproj b/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.ServiceDefaults/ExampleNamespace.ServiceDefaults.csproj index 8fd483346a..eeb71e38f4 100644 --- a/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.ServiceDefaults/ExampleNamespace.ServiceDefaults.csproj +++ b/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.ServiceDefaults/ExampleNamespace.ServiceDefaults.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable true @@ -10,13 +10,13 @@ - - - - - - - + + + + + + + diff --git a/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.TestProject/ExampleNamespace.TestProject.csproj b/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.TestProject/ExampleNamespace.TestProject.csproj index 3075c21f6c..deaf6f4190 100644 --- a/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.TestProject/ExampleNamespace.TestProject.csproj +++ b/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.TestProject/ExampleNamespace.TestProject.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable false @@ -10,8 +10,8 @@ - - + + diff --git a/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.WebApp/ExampleNamespace.WebApp.csproj b/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.WebApp/ExampleNamespace.WebApp.csproj index 55567ede91..659de24086 100644 --- a/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.WebApp/ExampleNamespace.WebApp.csproj +++ b/TUnit.Templates/content/TUnit.Aspire.Starter/ExampleNamespace.WebApp/ExampleNamespace.WebApp.csproj @@ -1,13 +1,13 @@ - net9.0 + net10.0 enable enable - + diff --git a/TUnit.Templates/content/TUnit.Aspire.Test/ExampleNamespace.csproj b/TUnit.Templates/content/TUnit.Aspire.Test/ExampleNamespace.csproj index 7da16ebe3d..645e71de21 100644 --- a/TUnit.Templates/content/TUnit.Aspire.Test/ExampleNamespace.csproj +++ b/TUnit.Templates/content/TUnit.Aspire.Test/ExampleNamespace.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable false @@ -9,8 +9,8 @@ - - + + diff --git a/TUnit.Templates/content/TUnit.FSharp/TestProject.fsproj b/TUnit.Templates/content/TUnit.FSharp/TestProject.fsproj index 87db30ee23..dc3cd9a2aa 100644 --- a/TUnit.Templates/content/TUnit.FSharp/TestProject.fsproj +++ b/TUnit.Templates/content/TUnit.FSharp/TestProject.fsproj @@ -9,9 +9,9 @@ - - - + + + diff --git a/TUnit.Templates/content/TUnit.Playwright/TestProject.csproj b/TUnit.Templates/content/TUnit.Playwright/TestProject.csproj index 25055774d9..66151ae388 100644 --- a/TUnit.Templates/content/TUnit.Playwright/TestProject.csproj +++ b/TUnit.Templates/content/TUnit.Playwright/TestProject.csproj @@ -8,7 +8,7 @@ - + diff --git a/TUnit.Templates/content/TUnit.VB/TestProject.vbproj b/TUnit.Templates/content/TUnit.VB/TestProject.vbproj index ff5e5de922..3e3057e7b4 100644 --- a/TUnit.Templates/content/TUnit.VB/TestProject.vbproj +++ b/TUnit.Templates/content/TUnit.VB/TestProject.vbproj @@ -8,6 +8,6 @@ - + diff --git a/TUnit.Templates/content/TUnit/TestProject.csproj b/TUnit.Templates/content/TUnit/TestProject.csproj index c375f81c6f..5affa06433 100644 --- a/TUnit.Templates/content/TUnit/TestProject.csproj +++ b/TUnit.Templates/content/TUnit/TestProject.csproj @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/TUnit.TestProject.FSharp/AsyncTests.fs b/TUnit.TestProject.FSharp/AsyncTests.fs new file mode 100644 index 0000000000..675fc756d0 --- /dev/null +++ b/TUnit.TestProject.FSharp/AsyncTests.fs @@ -0,0 +1,51 @@ +namespace TUnit.TestProject.FSharp + +open System.Threading.Tasks +open TUnit.Assertions +open TUnit.Assertions.Extensions +open TUnit.Assertions.FSharp.Operations +open TUnit.Core + +/// Tests to verify F# Async<'T> return types are properly executed +type AsyncTests() = + + /// Static tracker to verify tests actually execute + static member val private ExecutionCount = 0 with get, set + + [] + member _.FSharpAsync_BasicExecution() : Async = async { + AsyncTests.ExecutionCount <- 1 + do! Async.Sleep 10 + } + + [] + [] + member _.VerifyFSharpAsyncExecuted() = + // This test depends on the previous one and verifies it actually ran + if AsyncTests.ExecutionCount = 0 then + failwith "F# Async test did not execute!" + Task.CompletedTask + + [] + member _.FSharpAsync_WithReturnValue() : Async = async { + do! Async.Sleep 10 + return 42 + } + + [] + member _.FSharpAsync_WithAsyncSleep() : Async = async { + // Verify async operations work correctly + do! Async.Sleep 50 + } + + [] + member _.FSharpAsync_CallingTask() : Async = async { + // F# Async calling Task-based API + do! Task.Delay(10) |> Async.AwaitTask + } + + [] + member _.FSharpAsync_WithAssertion() : Async = async { + let result = 1 + 1 + do! check (Assert.That(result).IsEqualTo(2)) + } diff --git a/TUnit.TestProject.FSharp/TUnit.TestProject.FSharp.fsproj b/TUnit.TestProject.FSharp/TUnit.TestProject.FSharp.fsproj index 5fce858d96..a2eb49b6c7 100644 --- a/TUnit.TestProject.FSharp/TUnit.TestProject.FSharp.fsproj +++ b/TUnit.TestProject.FSharp/TUnit.TestProject.FSharp.fsproj @@ -18,6 +18,8 @@ + + diff --git a/TUnit.TestProject.FSharp/TaskAssertTests.fs b/TUnit.TestProject.FSharp/TaskAssertTests.fs new file mode 100644 index 0000000000..b2597ec104 --- /dev/null +++ b/TUnit.TestProject.FSharp/TaskAssertTests.fs @@ -0,0 +1,70 @@ +namespace TUnit.TestProject + +open System.Threading.Tasks +open TUnit.Assertions +open TUnit.Assertions.Extensions +open TUnit.Assertions.FSharp.TaskAssert +open TUnit.Core +open TUnit.TestProject.Library.Models + +type TaskAssertTests() = + + [] + [)>] + member _.IsInitialized_With_1_ClassDataSource(class1: InitializableClass) : Task = + taskAssert { + do! Assert.That(class1.IsInitialized).IsTrue() + } + + [] + [, typeof)>] + member _.IsInitialized_With_2_ClassDataSources(class1: InitializableClass, class2: InitializableClass) : Task = + taskAssert { + do! Assert.That(class1.IsInitialized).IsTrue() + do! Assert.That(class2.IsInitialized).IsTrue() + } + + [] + [, typeof, typeof)>] + member _.IsInitialized_With_3_ClassDataSources(class1: InitializableClass, class2: InitializableClass, class3: InitializableClass) : Task = + taskAssert { + do! Assert.That(class1.IsInitialized).IsTrue() + do! Assert.That(class2.IsInitialized).IsTrue() + do! Assert.That(class3.IsInitialized).IsTrue() + } + + [] + [, typeof, typeof, typeof)>] + member _.IsInitialized_With_4_ClassDataSources(class1: InitializableClass, class2: InitializableClass, class3: InitializableClass, class4: InitializableClass) : Task = + taskAssert { + do! Assert.That(class1.IsInitialized).IsTrue() + do! Assert.That(class2.IsInitialized).IsTrue() + do! Assert.That(class3.IsInitialized).IsTrue() + do! Assert.That(class4.IsInitialized).IsTrue() + } + + [] + [, typeof, typeof, typeof, typeof)>] + member _.IsInitialized_With_5_ClassDataSources(class1: InitializableClass, class2: InitializableClass, class3: InitializableClass, class4: InitializableClass, class5: InitializableClass) : Task = + taskAssert { + do! Assert.That(class1.IsInitialized).IsTrue() + do! Assert.That(class2.IsInitialized).IsTrue() + do! Assert.That(class3.IsInitialized).IsTrue() + do! Assert.That(class4.IsInitialized).IsTrue() + do! Assert.That(class5.IsInitialized).IsTrue() + } + + [] + member _.FailingAssertion_ThrowsCorrectly() : Task = + task { + let mutable exceptionThrown = false + try + do! taskAssert { + do! Assert.That(1 + 1).IsEqualTo(3) + } + with + | _ -> exceptionThrown <- true + + if not exceptionThrown then + failwith "Expected an exception to be thrown but none was thrown" + } diff --git a/TUnit.TestProject.Library/Polyfills/ModuleInitializerAttribute.cs b/TUnit.TestProject.Library/Polyfills/ModuleInitializerAttribute.cs deleted file mode 100644 index 49cce8f4f0..0000000000 --- a/TUnit.TestProject.Library/Polyfills/ModuleInitializerAttribute.cs +++ /dev/null @@ -1,5 +0,0 @@ -// ReSharper disable once CheckNamespace -namespace System.Runtime.CompilerServices; - -// ReSharper disable once RedundantExtendsListEntry -sealed class ModuleInitializerAttribute : Attribute; diff --git a/TUnit.TestProject/AfterTestAttributeTests.cs b/TUnit.TestProject/AfterTestAttributeTests.cs index 47e9022801..4aba172d1d 100644 --- a/TUnit.TestProject/AfterTestAttributeTests.cs +++ b/TUnit.TestProject/AfterTestAttributeTests.cs @@ -19,7 +19,7 @@ public async ValueTask OnTestEnd(TestContext testContext) { Console.WriteLine(@"Writing file inside WriteFileAfterTestAttribute!"); - await FilePolyfill.WriteAllTextAsync(Filename, "Foo!"); + await File.WriteAllTextAsync(Filename, "Foo!"); } public int Order => 0; diff --git a/TUnit.TestProject/AfterTests/TestDiscoveryAfterTests.cs b/TUnit.TestProject/AfterTests/TestDiscoveryAfterTests.cs index 9a7b82e62a..1adac76180 100644 --- a/TUnit.TestProject/AfterTests/TestDiscoveryAfterTests.cs +++ b/TUnit.TestProject/AfterTests/TestDiscoveryAfterTests.cs @@ -11,7 +11,7 @@ public static async Task AfterTestDiscovery(TestDiscoveryContext context) [AfterEvery(TestDiscovery)] public static async Task AfterEveryTestDiscovery(TestDiscoveryContext context) { - await FilePolyfill.WriteAllTextAsync($"TestDiscoveryAfterTests{Guid.NewGuid():N}.txt", $"{context.AllTests.Count()} tests found"); + await File.WriteAllTextAsync($"TestDiscoveryAfterTests{Guid.NewGuid():N}.txt", $"{context.AllTests.Count()} tests found"); var test = context.AllTests.FirstOrDefault(x => x.Metadata.TestDetails.TestName == nameof(TestDiscoveryAfterTests.EnsureAfterEveryTestDiscoveryHit)); diff --git a/TUnit.TestProject/AfterTests/TestSessionAfterTests.cs b/TUnit.TestProject/AfterTests/TestSessionAfterTests.cs index 384c5f3ba2..34dc0788fb 100644 --- a/TUnit.TestProject/AfterTests/TestSessionAfterTests.cs +++ b/TUnit.TestProject/AfterTests/TestSessionAfterTests.cs @@ -11,7 +11,7 @@ public static async Task AfterTestSession(TestSessionContext context) [AfterEvery(TestSession)] public static async Task AfterEveryTestSession(TestSessionContext context) { - await FilePolyfill.WriteAllTextAsync($"TestSessionAfterTests{Guid.NewGuid():N}.txt", $"{context.AllTests.Count()} tests in session"); + await File.WriteAllTextAsync($"TestSessionAfterTests{Guid.NewGuid():N}.txt", $"{context.AllTests.Count()} tests in session"); var test = context.AllTests.FirstOrDefault(x => x.Metadata.TestDetails.TestName == nameof(TestSessionAfterTests.PepareForAfterSession)); diff --git a/TUnit.TestProject/BeforeTests/TestDiscoveryBeforeTests.cs b/TUnit.TestProject/BeforeTests/TestDiscoveryBeforeTests.cs index da3412f221..73b5060e00 100644 --- a/TUnit.TestProject/BeforeTests/TestDiscoveryBeforeTests.cs +++ b/TUnit.TestProject/BeforeTests/TestDiscoveryBeforeTests.cs @@ -11,7 +11,7 @@ public static async Task BeforeTestDiscovery(BeforeTestDiscoveryContext context) [BeforeEvery(TestDiscovery)] public static async Task BeforeEveryTestDiscovery(BeforeTestDiscoveryContext context) { - await FilePolyfill.WriteAllTextAsync($"TestDiscoveryBeforeTests{Guid.NewGuid():N}.txt", $"Blah!"); + await File.WriteAllTextAsync($"TestDiscoveryBeforeTests{Guid.NewGuid():N}.txt", $"Blah!"); } } diff --git a/TUnit.TestProject/BeforeTests/TestSessionBeforeTests.cs b/TUnit.TestProject/BeforeTests/TestSessionBeforeTests.cs index 8c443ef421..8baf92eadd 100644 --- a/TUnit.TestProject/BeforeTests/TestSessionBeforeTests.cs +++ b/TUnit.TestProject/BeforeTests/TestSessionBeforeTests.cs @@ -11,7 +11,7 @@ public static async Task BeforeTestSession(TestSessionContext context) [BeforeEvery(TestSession)] public static async Task BeforeEveryTestSession(TestSessionContext context) { - await FilePolyfill.WriteAllTextAsync($"TestSessionBeforeTests{Guid.NewGuid():N}.txt", $"{context.AllTests.Count()} tests in session"); + await File.WriteAllTextAsync($"TestSessionBeforeTests{Guid.NewGuid():N}.txt", $"{context.AllTests.Count()} tests in session"); var test = context.AllTests.FirstOrDefault(x => x.Metadata.TestDetails.TestName == nameof(TestSessionBeforeTests.EnsureBeforeEveryTestSessionHit)); diff --git a/TUnit.TestProject/Bugs/3266/Tests.cs b/TUnit.TestProject/Bugs/3266/Tests.cs index f18ef81aed..25dadb37fa 100644 --- a/TUnit.TestProject/Bugs/3266/Tests.cs +++ b/TUnit.TestProject/Bugs/3266/Tests.cs @@ -1,17 +1,42 @@ +using System.Collections.Concurrent; using TUnit.Core; using TUnit.Core.Interfaces; using TUnit.TestProject.Attributes; namespace TUnit.TestProject.Bugs.Bug3266; +// Global tracker to verify initialization order across all components +public static class InitializationOrderTracker +{ + private static int _counter; + private static readonly ConcurrentDictionary InitOrder = new(); + + public static int RecordInitialization(string componentName) + { + var order = Interlocked.Increment(ref _counter); + InitOrder[componentName] = order; + return order; + } + + public static int GetOrder(string componentName) => InitOrder.TryGetValue(componentName, out var order) ? order : -1; + + public static void Reset() + { + _counter = 0; + InitOrder.Clear(); + } +} + // Mock test container - shared per test session public class PulsarTestContainer : IAsyncInitializer, IAsyncDisposable { public bool IsInitialized { get; private set; } public bool IsDisposed { get; private set; } + public int InitializationOrder { get; private set; } public Task InitializeAsync() { + InitializationOrder = InitializationOrderTracker.RecordInitialization(nameof(PulsarTestContainer)); IsInitialized = true; return Task.CompletedTask; } @@ -31,6 +56,7 @@ public class PulsarConnection : IAsyncInitializer, IAsyncDisposable public bool IsInitialized { get; private set; } public bool IsDisposed { get; private set; } + public int InitializationOrder { get; private set; } public Task InitializeAsync() { @@ -41,6 +67,7 @@ public Task InitializeAsync() "PulsarConnection.InitializeAsync() called before nested Container property was initialized!"); } + InitializationOrder = InitializationOrderTracker.RecordInitialization(nameof(PulsarConnection)); IsInitialized = true; return Task.CompletedTask; } @@ -60,6 +87,7 @@ public class WebAppFactory : IAsyncInitializer, IAsyncDisposable public bool IsInitialized { get; private set; } public bool IsDisposed { get; private set; } + public int InitializationOrder { get; private set; } public Task InitializeAsync() { @@ -70,6 +98,7 @@ public Task InitializeAsync() "WebAppFactory.InitializeAsync() called before nested Container property was initialized!"); } + InitializationOrder = InitializationOrderTracker.RecordInitialization(nameof(WebAppFactory)); IsInitialized = true; return Task.CompletedTask; } @@ -130,4 +159,280 @@ await Assert.That(Connection.Container) .IsSameReferenceAs(WebApp.Container) .Because("Both properties should share the same PulsarTestContainer instance"); } + + [Test] + public async Task InitializationOrderShouldBeDependencyFirst() + { + // PulsarTestContainer must be initialized before both PulsarConnection and WebAppFactory + await Assert.That(Connection.Container.InitializationOrder) + .IsLessThan(Connection.InitializationOrder) + .Because("Container should be initialized before PulsarConnection"); + + await Assert.That(WebApp.Container.InitializationOrder) + .IsLessThan(WebApp.InitializationOrder) + .Because("Container should be initialized before WebAppFactory"); + } +} + +#region Deep Nested Dependency Chain Tests + +// Level 1: Deepest shared dependency +public class DeepDependencyLevel1 : IAsyncInitializer, IAsyncDisposable +{ + public bool IsInitialized { get; private set; } + public int InitializationOrder { get; private set; } + + public Task InitializeAsync() + { + InitializationOrder = InitializationOrderTracker.RecordInitialization($"{nameof(DeepDependencyLevel1)}_{GetHashCode()}"); + IsInitialized = true; + return Task.CompletedTask; + } + + public ValueTask DisposeAsync() => default; +} + +// Level 2: Depends on Level 1 +public class DeepDependencyLevel2 : IAsyncInitializer, IAsyncDisposable +{ + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required DeepDependencyLevel1 Level1 { get; init; } + + public bool IsInitialized { get; private set; } + public int InitializationOrder { get; private set; } + + public Task InitializeAsync() + { + if (!Level1.IsInitialized) + { + throw new InvalidOperationException("Level2 initialized before Level1!"); + } + InitializationOrder = InitializationOrderTracker.RecordInitialization($"{nameof(DeepDependencyLevel2)}_{GetHashCode()}"); + IsInitialized = true; + return Task.CompletedTask; + } + + public ValueTask DisposeAsync() => default; +} + +// Level 3: Depends on Level 2 +public class DeepDependencyLevel3 : IAsyncInitializer, IAsyncDisposable +{ + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required DeepDependencyLevel2 Level2 { get; init; } + + public bool IsInitialized { get; private set; } + public int InitializationOrder { get; private set; } + + public Task InitializeAsync() + { + if (!Level2.IsInitialized) + { + throw new InvalidOperationException("Level3 initialized before Level2!"); + } + if (!Level2.Level1.IsInitialized) + { + throw new InvalidOperationException("Level3 initialized before Level1!"); + } + InitializationOrder = InitializationOrderTracker.RecordInitialization($"{nameof(DeepDependencyLevel3)}_{GetHashCode()}"); + IsInitialized = true; + return Task.CompletedTask; + } + + public ValueTask DisposeAsync() => default; +} + +// Level 4: Depends on Level 3 (deepest level in the chain) +public class DeepDependencyLevel4 : IAsyncInitializer, IAsyncDisposable +{ + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required DeepDependencyLevel3 Level3 { get; init; } + + public bool IsInitialized { get; private set; } + public int InitializationOrder { get; private set; } + + public Task InitializeAsync() + { + if (!Level3.IsInitialized) + { + throw new InvalidOperationException("Level4 initialized before Level3!"); + } + InitializationOrder = InitializationOrderTracker.RecordInitialization($"{nameof(DeepDependencyLevel4)}_{GetHashCode()}"); + IsInitialized = true; + return Task.CompletedTask; + } + + public ValueTask DisposeAsync() => default; +} + +// Abstract base class in deep chain +public abstract class DeepAbstractBase +{ + [ClassDataSource(Shared = SharedType.None)] + public required DeepDependencyLevel4 DeepDependency { get; init; } +} + +// Concrete test for 4-level deep dependency chain +[EngineTest(ExpectedResult.Pass)] +[NotInParallel] +public class DeepDependencyChainTest : DeepAbstractBase +{ + [Test] + public async Task FourLevelDeepChainShouldInitializeInCorrectOrder() + { + // Verify all levels are initialized + await Assert.That(DeepDependency.Level3.Level2.Level1.IsInitialized).IsTrue(); + await Assert.That(DeepDependency.Level3.Level2.IsInitialized).IsTrue(); + await Assert.That(DeepDependency.Level3.IsInitialized).IsTrue(); + await Assert.That(DeepDependency.IsInitialized).IsTrue(); + + // Verify initialization order: Level1 < Level2 < Level3 < Level4 + await Assert.That(DeepDependency.Level3.Level2.Level1.InitializationOrder) + .IsLessThan(DeepDependency.Level3.Level2.InitializationOrder); + + await Assert.That(DeepDependency.Level3.Level2.InitializationOrder) + .IsLessThan(DeepDependency.Level3.InitializationOrder); + + await Assert.That(DeepDependency.Level3.InitializationOrder) + .IsLessThan(DeepDependency.InitializationOrder); + } +} + +#endregion + +#region MethodDataSource with Property Injection Tests + +// A fixture that uses property injection and provides data via MethodDataSource +public class FixtureWithMethodDataSource : IAsyncInitializer, IAsyncDisposable +{ + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required PulsarTestContainer Container { get; init; } + + public bool IsInitialized { get; private set; } + + public Task InitializeAsync() + { + if (!Container.IsInitialized) + { + throw new InvalidOperationException("FixtureWithMethodDataSource initialized before its Container!"); + } + IsInitialized = true; + return Task.CompletedTask; + } + + public ValueTask DisposeAsync() => default; + + // This method provides test data and requires the fixture to be initialized + public IEnumerable> GetTestData() + { + if (!IsInitialized) + { + throw new InvalidOperationException("GetTestData called before fixture was initialized!"); + } + yield return () => "TestValue1"; + yield return () => "TestValue2"; + } +} + +// Test that combines MethodDataSource with ClassDataSource property injection +[EngineTest(ExpectedResult.Pass)] +[NotInParallel] +public class MethodDataSourceWithPropertyInjectionTest +{ + [ClassDataSource(Shared = SharedType.PerClass)] + public required FixtureWithMethodDataSource Fixture { get; init; } + + public IEnumerable> TestData => Fixture.GetTestData(); + + [Test] + [MethodDataSource(nameof(TestData))] + public async Task TestWithMethodDataFromInjectedFixture(string testValue) + { + // If we reach here, the initialization order was correct + await Assert.That(Fixture.IsInitialized).IsTrue(); + await Assert.That(Fixture.Container.IsInitialized).IsTrue(); + await Assert.That(testValue).IsNotNullOrEmpty(); + } +} + +#endregion + +#region Multiple Abstract Base Classes with Different Sharing Modes + +// Shared per class container +public class PerClassContainer : IAsyncInitializer, IAsyncDisposable +{ + public bool IsInitialized { get; private set; } + public string InstanceId { get; } = Guid.NewGuid().ToString(); + + public Task InitializeAsync() + { + IsInitialized = true; + return Task.CompletedTask; + } + + public ValueTask DisposeAsync() => default; +} + +// Dependency that uses PerClass shared container +public class PerClassDependency : IAsyncInitializer, IAsyncDisposable +{ + [ClassDataSource(Shared = SharedType.PerClass)] + public required PerClassContainer Container { get; init; } + + public bool IsInitialized { get; private set; } + + public Task InitializeAsync() + { + if (!Container.IsInitialized) + { + throw new InvalidOperationException("PerClassDependency initialized before Container!"); + } + IsInitialized = true; + return Task.CompletedTask; + } + + public ValueTask DisposeAsync() => default; +} + +// First abstract base with PerClass dependency +public abstract class AbstractWithPerClassDependency +{ + [ClassDataSource(Shared = SharedType.None)] + public required PerClassDependency PerClassDep { get; init; } +} + +// Second abstract layer with additional PerTestSession dependency +public abstract class AbstractWithMixedSharing : AbstractWithPerClassDependency +{ + [ClassDataSource(Shared = SharedType.None)] + public required PulsarConnection SessionDep { get; init; } +} + +// Test for mixed sharing modes in abstract hierarchy +[EngineTest(ExpectedResult.Pass)] +[NotInParallel] +public class MixedSharingModesInAbstractHierarchyTest : AbstractWithMixedSharing +{ + [Test] + public async Task AllDependenciesShouldBeProperlyInitialized() + { + // Verify PerClass chain + await Assert.That(PerClassDep.IsInitialized).IsTrue(); + await Assert.That(PerClassDep.Container.IsInitialized).IsTrue(); + + // Verify PerTestSession chain + await Assert.That(SessionDep.IsInitialized).IsTrue(); + await Assert.That(SessionDep.Container.IsInitialized).IsTrue(); + } + + [Test] + public async Task ContainersWithSameSharingModeAndTypeShouldBeShared() + { + // Both PulsarConnection and WebAppFactory should share the same PulsarTestContainer + // because they both use SharedType.PerTestSession for PulsarTestContainer + await Assert.That(SessionDep.Container).IsNotNull(); + } } + +#endregion diff --git a/TUnit.TestProject/Bugs/3803/TestRabbitContainer.cs b/TUnit.TestProject/Bugs/3803/TestRabbitContainer.cs new file mode 100644 index 0000000000..58c1a14622 --- /dev/null +++ b/TUnit.TestProject/Bugs/3803/TestRabbitContainer.cs @@ -0,0 +1,51 @@ +using TUnit.Core.Interfaces; + +namespace TUnit.TestProject.Bugs._3803; + +/// +/// Simulates a RabbitMQ test container. +/// This class should be instantiated only once per test session when marked as SharedType.PerTestSession. +/// +public class TestRabbitContainer : IAsyncInitializer, IAsyncDisposable +{ + private static int _instanceCount = 0; + private static int _initializeCount = 0; + private static int _disposeCount = 0; + + public static int InstanceCount => _instanceCount; + public static int InitializeCount => _initializeCount; + public static int DisposeCount => _disposeCount; + + public int InstanceId { get; } + public bool IsInitialized { get; private set; } + public bool IsDisposed { get; private set; } + + public TestRabbitContainer() + { + InstanceId = Interlocked.Increment(ref _instanceCount); + Console.WriteLine($"[TestRabbitContainer] Constructor called - Instance #{InstanceId}"); + } + + public Task InitializeAsync() + { + Interlocked.Increment(ref _initializeCount); + IsInitialized = true; + Console.WriteLine($"[TestRabbitContainer] InitializeAsync called - Instance #{InstanceId}"); + return Task.CompletedTask; + } + + public ValueTask DisposeAsync() + { + Interlocked.Increment(ref _disposeCount); + IsDisposed = true; + Console.WriteLine($"[TestRabbitContainer] DisposeAsync called - Instance #{InstanceId}"); + return default; + } + + public static void ResetCounters() + { + _instanceCount = 0; + _initializeCount = 0; + _disposeCount = 0; + } +} diff --git a/TUnit.TestProject/Bugs/3803/TestSqlContainer.cs b/TUnit.TestProject/Bugs/3803/TestSqlContainer.cs new file mode 100644 index 0000000000..57c6f317db --- /dev/null +++ b/TUnit.TestProject/Bugs/3803/TestSqlContainer.cs @@ -0,0 +1,51 @@ +using TUnit.Core.Interfaces; + +namespace TUnit.TestProject.Bugs._3803; + +/// +/// Simulates a SQL test container. +/// This class should be instantiated only once per test session when marked as SharedType.PerTestSession. +/// +public class TestSqlContainer : IAsyncInitializer, IAsyncDisposable +{ + private static int _instanceCount = 0; + private static int _initializeCount = 0; + private static int _disposeCount = 0; + + public static int InstanceCount => _instanceCount; + public static int InitializeCount => _initializeCount; + public static int DisposeCount => _disposeCount; + + public int InstanceId { get; } + public bool IsInitialized { get; private set; } + public bool IsDisposed { get; private set; } + + public TestSqlContainer() + { + InstanceId = Interlocked.Increment(ref _instanceCount); + Console.WriteLine($"[TestSqlContainer] Constructor called - Instance #{InstanceId}"); + } + + public Task InitializeAsync() + { + Interlocked.Increment(ref _initializeCount); + IsInitialized = true; + Console.WriteLine($"[TestSqlContainer] InitializeAsync called - Instance #{InstanceId}"); + return Task.CompletedTask; + } + + public ValueTask DisposeAsync() + { + Interlocked.Increment(ref _disposeCount); + IsDisposed = true; + Console.WriteLine($"[TestSqlContainer] DisposeAsync called - Instance #{InstanceId}"); + return default; + } + + public static void ResetCounters() + { + _instanceCount = 0; + _initializeCount = 0; + _disposeCount = 0; + } +} diff --git a/TUnit.TestProject/Bugs/3803/Tests.cs b/TUnit.TestProject/Bugs/3803/Tests.cs new file mode 100644 index 0000000000..556c173d52 --- /dev/null +++ b/TUnit.TestProject/Bugs/3803/Tests.cs @@ -0,0 +1,135 @@ +using TUnit.TestProject.Attributes; + +namespace TUnit.TestProject.Bugs._3803; + +/// +/// Bug #3803: Nested dependencies with SharedType.PerTestSession are instantiated multiple times +/// +/// Expected behavior: +/// - TestRabbitContainer should be instantiated ONCE per test session (InstanceCount == 1) +/// - TestSqlContainer should be instantiated ONCE per test session (InstanceCount == 1) +/// - All WebApplicationFactory instances should receive the SAME container instances +/// +/// Actual behavior (BUG): +/// - Containers are instantiated multiple times (once per test or once per factory) +/// - Each WebApplicationFactory receives DIFFERENT container instances +/// + +[NotInParallel] +[EngineTest(ExpectedResult.Pass)] +public class Bug3803_TestClass1 +{ + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required WebApplicationFactory Factory { get; init; } + + [Test] + public async Task Test1_VerifyContainersAreShared() + { + Console.WriteLine($"[Bug3803_TestClass1.Test1] Factory Instance: #{Factory.InstanceId}"); + Console.WriteLine($"[Bug3803_TestClass1.Test1] RabbitContainer Instance: #{Factory.RabbitContainer.InstanceId}"); + Console.WriteLine($"[Bug3803_TestClass1.Test1] SqlContainer Instance: #{Factory.SqlContainer.InstanceId}"); + + // Verify containers are initialized + await Assert.That(Factory.RabbitContainer.IsInitialized).IsTrue(); + await Assert.That(Factory.SqlContainer.IsInitialized).IsTrue(); + + // BUG VERIFICATION: These should ALWAYS be 1 if SharedType.PerTestSession works correctly + await Assert.That(TestRabbitContainer.InstanceCount).IsEqualTo(1); + await Assert.That(TestSqlContainer.InstanceCount).IsEqualTo(1); + + // All instances should have ID = 1 (first and only instance) + await Assert.That(Factory.RabbitContainer.InstanceId).IsEqualTo(1); + await Assert.That(Factory.SqlContainer.InstanceId).IsEqualTo(1); + } + + [Test] + public async Task Test2_VerifyContainersAreStillShared() + { + Console.WriteLine($"[Bug3803_TestClass1.Test2] Factory Instance: #{Factory.InstanceId}"); + Console.WriteLine($"[Bug3803_TestClass1.Test2] RabbitContainer Instance: #{Factory.RabbitContainer.InstanceId}"); + Console.WriteLine($"[Bug3803_TestClass1.Test2] SqlContainer Instance: #{Factory.SqlContainer.InstanceId}"); + + // Same assertions as Test1 - containers should still be the same instances + await Assert.That(TestRabbitContainer.InstanceCount).IsEqualTo(1); + await Assert.That(TestSqlContainer.InstanceCount).IsEqualTo(1); + await Assert.That(Factory.RabbitContainer.InstanceId).IsEqualTo(1); + await Assert.That(Factory.SqlContainer.InstanceId).IsEqualTo(1); + } + + [Test] + public async Task Test3_VerifyInitializationCalledOnce() + { + Console.WriteLine($"[Bug3803_TestClass1.Test3] RabbitContainer InitializeCount: {TestRabbitContainer.InitializeCount}"); + Console.WriteLine($"[Bug3803_TestClass1.Test3] SqlContainer InitializeCount: {TestSqlContainer.InitializeCount}"); + + // Initialize should be called only once per container + await Assert.That(TestRabbitContainer.InitializeCount).IsEqualTo(1); + await Assert.That(TestSqlContainer.InitializeCount).IsEqualTo(1); + } +} + +[NotInParallel] +[EngineTest(ExpectedResult.Pass)] +public class Bug3803_TestClass2 +{ + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required WebApplicationFactory Factory { get; init; } + + [Test] + public async Task Test1_DifferentClassShouldGetSameContainers() + { + Console.WriteLine($"[Bug3803_TestClass2.Test1] Factory Instance: #{Factory.InstanceId}"); + Console.WriteLine($"[Bug3803_TestClass2.Test1] RabbitContainer Instance: #{Factory.RabbitContainer.InstanceId}"); + Console.WriteLine($"[Bug3803_TestClass2.Test1] SqlContainer Instance: #{Factory.SqlContainer.InstanceId}"); + + // Even in a different test class, we should get the SAME container instances + await Assert.That(TestRabbitContainer.InstanceCount).IsEqualTo(1); + await Assert.That(TestSqlContainer.InstanceCount).IsEqualTo(1); + + // Should be the same instance (ID = 1) + await Assert.That(Factory.RabbitContainer.InstanceId).IsEqualTo(1); + await Assert.That(Factory.SqlContainer.InstanceId).IsEqualTo(1); + } + + [Test] + public async Task Test2_VerifyContainersAreInitialized() + { + await Assert.That(Factory.RabbitContainer.IsInitialized).IsTrue(); + await Assert.That(Factory.SqlContainer.IsInitialized).IsTrue(); + } +} + +[NotInParallel] +[EngineTest(ExpectedResult.Pass)] +public class Bug3803_TestClass3 +{ + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required WebApplicationFactory Factory { get; init; } + + [Test] + public async Task Test1_ThirdClassAlsoGetsSameContainers() + { + Console.WriteLine($"[Bug3803_TestClass3.Test1] Factory Instance: #{Factory.InstanceId}"); + Console.WriteLine($"[Bug3803_TestClass3.Test1] RabbitContainer Instance: #{Factory.RabbitContainer.InstanceId}"); + Console.WriteLine($"[Bug3803_TestClass3.Test1] SqlContainer Instance: #{Factory.SqlContainer.InstanceId}"); + + // Still the same instances + await Assert.That(TestRabbitContainer.InstanceCount).IsEqualTo(1); + await Assert.That(TestSqlContainer.InstanceCount).IsEqualTo(1); + await Assert.That(Factory.RabbitContainer.InstanceId).IsEqualTo(1); + await Assert.That(Factory.SqlContainer.InstanceId).IsEqualTo(1); + } + + [Test] + public async Task Test2_FinalVerification() + { + Console.WriteLine($"[Bug3803_TestClass3.Test2] Final verification"); + Console.WriteLine($" Total RabbitContainer instances: {TestRabbitContainer.InstanceCount}"); + Console.WriteLine($" Total SqlContainer instances: {TestSqlContainer.InstanceCount}"); + Console.WriteLine($" Total WebApplicationFactory instances: {WebApplicationFactory.InstanceCount}"); + + // Final assertion: containers should have been instantiated exactly once + await Assert.That(TestRabbitContainer.InstanceCount).IsEqualTo(1); + await Assert.That(TestSqlContainer.InstanceCount).IsEqualTo(1); + } +} diff --git a/TUnit.TestProject/Bugs/3803/WebApplicationFactory.cs b/TUnit.TestProject/Bugs/3803/WebApplicationFactory.cs new file mode 100644 index 0000000000..3172f4d792 --- /dev/null +++ b/TUnit.TestProject/Bugs/3803/WebApplicationFactory.cs @@ -0,0 +1,61 @@ +using TUnit.Core.Interfaces; + +namespace TUnit.TestProject.Bugs._3803; + +/// +/// Simulates a WebApplicationFactory that depends on test containers. +/// The containers should be shared (SharedType.PerTestSession), meaning: +/// - Each container should be instantiated only ONCE per test session +/// - All instances of WebApplicationFactory should receive the SAME container instances +/// +public class WebApplicationFactory : IAsyncInitializer, IAsyncDisposable +{ + private static int _instanceCount = 0; + private static int _initializeCount = 0; + private static int _disposeCount = 0; + + public static int InstanceCount => _instanceCount; + public static int InitializeCount => _initializeCount; + public static int DisposeCount => _disposeCount; + + public int InstanceId { get; } + public bool IsInitialized { get; private set; } + public bool IsDisposed { get; private set; } + + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required TestRabbitContainer RabbitContainer { get; init; } + + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required TestSqlContainer SqlContainer { get; init; } + + public WebApplicationFactory() + { + InstanceId = Interlocked.Increment(ref _instanceCount); + Console.WriteLine($"[WebApplicationFactory] Constructor called - Instance #{InstanceId}"); + } + + public Task InitializeAsync() + { + Interlocked.Increment(ref _initializeCount); + IsInitialized = true; + Console.WriteLine($"[WebApplicationFactory] InitializeAsync called - Instance #{InstanceId}"); + Console.WriteLine($" -> RabbitContainer Instance: #{RabbitContainer.InstanceId}"); + Console.WriteLine($" -> SqlContainer Instance: #{SqlContainer.InstanceId}"); + return Task.CompletedTask; + } + + public ValueTask DisposeAsync() + { + Interlocked.Increment(ref _disposeCount); + IsDisposed = true; + Console.WriteLine($"[WebApplicationFactory] DisposeAsync called - Instance #{InstanceId}"); + return default; + } + + public static void ResetCounters() + { + _instanceCount = 0; + _initializeCount = 0; + _disposeCount = 0; + } +} diff --git a/TUnit.TestProject/Bugs/3813/BaseServiceTests.cs b/TUnit.TestProject/Bugs/3813/BaseServiceTests.cs new file mode 100644 index 0000000000..f435689105 --- /dev/null +++ b/TUnit.TestProject/Bugs/3813/BaseServiceTests.cs @@ -0,0 +1,16 @@ +namespace TUnit.TestProject.Bugs._3813; + +public abstract class BaseServiceTests +{ + [Test] + public async Task BasicFeature() + { + await Task.CompletedTask; + } + + [Test] + public async Task AdvancedFeature() + { + await Task.CompletedTask; + } +} diff --git a/TUnit.TestProject/Bugs/3813/ImplementationATests.cs b/TUnit.TestProject/Bugs/3813/ImplementationATests.cs new file mode 100644 index 0000000000..75f2c52d8d --- /dev/null +++ b/TUnit.TestProject/Bugs/3813/ImplementationATests.cs @@ -0,0 +1,12 @@ +namespace TUnit.TestProject.Bugs._3813; + +[InheritsTests] +public class ImplementationATests : BaseServiceTests +{ + [Test] + [Skip("Implementation A does not support advanced feature")] + public new Task AdvancedFeature() + { + return Task.CompletedTask; + } +} diff --git a/TUnit.TestProject/Bugs/3939/Tests.cs b/TUnit.TestProject/Bugs/3939/Tests.cs new file mode 100644 index 0000000000..215a5be18c --- /dev/null +++ b/TUnit.TestProject/Bugs/3939/Tests.cs @@ -0,0 +1,53 @@ +using System.Diagnostics.CodeAnalysis; +using TUnit.Core.Interfaces; +using TUnit.TestProject.Attributes; + +namespace TUnit.TestProject.Bugs._3939; + +/// +/// Regression test for issue #3939: IClassConstructor and ITestEndEventReceiver +/// should use the same instance so that state can be shared (e.g., DI scope disposal). +/// +public sealed class ScopedClassConstructor : IClassConstructor, ITestEndEventReceiver +{ + private object? _scope; + + public Task Create([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type, ClassConstructorMetadata classConstructorMetadata) + { + // Simulate creating a DI scope - this should be available in OnTestEnd + _scope = new object(); + return Task.FromResult(new Tests()); + } + + public ValueTask OnTestEnd(TestContext context) + { + // This should be called on the SAME instance that Create() was called on + // If _scope is null, it means a different instance was used - this is the bug! + if (_scope == null) + { + throw new InvalidOperationException( + "Issue #3939: _scope was null in OnTestEnd(), indicating a different IClassConstructor " + + "instance was used than the one where Create() was called. " + + "IClassConstructor and ITestEndEventReceiver must use the same instance."); + } + + // Simulate disposing the scope + _scope = null; + return default; + } + + public int Order => 0; +} + +[EngineTest(ExpectedResult.Pass)] +[ClassConstructor] +public sealed class Tests +{ + [Test] + public Task TestMethod() + { + // The actual test - just verifies the test runs + // The real assertion is in OnTestEnd - it will throw if different instances are used + return Task.CompletedTask; + } +} diff --git a/TUnit.TestProject/Bugs/3951/Tests.cs b/TUnit.TestProject/Bugs/3951/Tests.cs new file mode 100644 index 0000000000..7bcb4c6e14 --- /dev/null +++ b/TUnit.TestProject/Bugs/3951/Tests.cs @@ -0,0 +1,50 @@ +using TUnit.Core.Interfaces; +using TUnit.TestProject.Attributes; + +namespace TUnit.TestProject.Bugs._3951; + + +public sealed class MyTestGeneratorAttribute : DataSourceGeneratorAttribute where T : MyType, new() +{ + protected override IEnumerable> GenerateDataSources(DataGeneratorMetadata dataGeneratorMetadata) => [() => new T()]; +} + +public class MyType; + +public class ErrContext: IAsyncInitializer, IAsyncDisposable +{ + public ValueTask DisposeAsync() => default; + public Task InitializeAsync() => Task.CompletedTask; +} + +public class ErrFixture : IAsyncDisposable, IAsyncInitializer +{ + [ClassDataSource(Shared = SharedType.PerClass)] + public required ErrContext Fixture { get; set; } + public ValueTask DisposeAsync() => default; + public Task InitializeAsync() => Task.CompletedTask; +} + +[EngineTest(ExpectedResult.Pass)] +public class ErrTest +{ + [ClassDataSource>(Shared = SharedType.PerClass)] + public required ErrFixture Fixture { get; init; } + + public IEnumerable> TestExecutions => [() => Fixture.Fixture]; + + [MethodDataSource("TestExecutions")] + + [Test] + public async Task MyTest(ErrContext context) + { + await Assert.That(context.GetType()).IsNotAssignableTo(); + } + + [MyTestGeneratorAttribute] + [Test] + public async Task MyTest2(MyType t) + { + await Assert.That(t).IsAssignableTo(); + } +} diff --git a/TUnit.TestProject/Bugs/3990/ClassLevelCombinedDataSourcesTests.cs b/TUnit.TestProject/Bugs/3990/ClassLevelCombinedDataSourcesTests.cs new file mode 100644 index 0000000000..81bb4a7f5d --- /dev/null +++ b/TUnit.TestProject/Bugs/3990/ClassLevelCombinedDataSourcesTests.cs @@ -0,0 +1,153 @@ +using TUnit.TestProject.Attributes; + +namespace TUnit.TestProject.Bugs._3990; + +/// +/// Tests for GitHub Issue #3990: Support CombinedDataSources on Class level +/// + +#region Test 1: Class-level CombinedDataSources with static Arguments + +[EngineTest(ExpectedResult.Pass)] +[CombinedDataSources] +public class ClassLevelCombinedDataSources_WithStaticArguments( + [Arguments(1, 2, 3)] int x, + [Arguments("a", "b")] string y) +{ + [Test] + public async Task Test_ShouldReceiveConstructorParameters() + { + // Should create 3 x 2 = 6 test cases + await Assert.That(x).IsIn([1, 2, 3]); + await Assert.That(y).IsIn(["a", "b"]); + } +} + +#endregion + +#region Test 2: Class-level CombinedDataSources with static MethodDataSource + +public class ClassLevelDataProviders +{ + public static IEnumerable GetNumbers() + { + yield return 10; + yield return 20; + } + + public static IEnumerable GetStrings() + { + yield return "Hello"; + yield return "World"; + } + + public static IEnumerable GetColors() + { + yield return "Red"; + yield return "Blue"; + yield return "Green"; + } +} + +[EngineTest(ExpectedResult.Pass)] +[CombinedDataSources] +public class ClassLevelCombinedDataSources_WithStaticMethodDataSource( + [MethodDataSource(nameof(ClassLevelDataProviders.GetNumbers))] int number, + [MethodDataSource(nameof(ClassLevelDataProviders.GetStrings))] string text) +{ + [Test] + public async Task Test_ShouldReceiveDataFromStaticMethods() + { + // Should create 2 x 2 = 4 test cases + await Assert.That(number).IsIn([10, 20]); + await Assert.That(text).IsIn(["Hello", "World"]); + } +} + +#endregion + +#region Test 3: Class-level CombinedDataSources mixed with method-level data sources + +[EngineTest(ExpectedResult.Pass)] +[CombinedDataSources] +public class ClassLevelCombinedDataSources_MixedWithMethodLevel( + [Arguments(1, 2)] int classArg, + [Arguments("A", "B")] string classText) +{ + [Test] + [CombinedDataSources] + public async Task Test_ShouldCombineClassAndMethodData( + [Arguments(100, 200)] int methodArg, + [Arguments("X", "Y")] string methodText) + { + // Class: 2 x 2 = 4 combinations + // Method: 2 x 2 = 4 combinations + // Total: 4 x 4 = 16 test cases + await Assert.That(classArg).IsIn([1, 2]); + await Assert.That(classText).IsIn(["A", "B"]); + await Assert.That(methodArg).IsIn([100, 200]); + await Assert.That(methodText).IsIn(["X", "Y"]); + } +} + +#endregion + +#region Test 4: Class-level CombinedDataSources with mixed data source types + +[EngineTest(ExpectedResult.Pass)] +[CombinedDataSources] +public class ClassLevelCombinedDataSources_MixedDataSourceTypes( + [Arguments(1, 2)] int number, + [MethodDataSource(nameof(ClassLevelDataProviders.GetColors))] string color) +{ + [Test] + public async Task Test_ShouldMixArgumentsAndMethodDataSource() + { + // Should create 2 x 3 = 6 test cases + await Assert.That(number).IsIn([1, 2]); + await Assert.That(color).IsIn(["Red", "Blue", "Green"]); + } +} + +#endregion + +#region Test 5: Class-level CombinedDataSources with three constructor parameters + +[EngineTest(ExpectedResult.Pass)] +[CombinedDataSources] +public class ClassLevelCombinedDataSources_ThreeParameters( + [Arguments(1, 2)] int x, + [Arguments("a", "b")] string y, + [Arguments(true, false)] bool z) +{ + [Test] + public async Task Test_ShouldHandleThreeParameters() + { + // Should create 2 x 2 x 2 = 8 test cases + await Assert.That(x).IsIn([1, 2]); + await Assert.That(y).IsIn(["a", "b"]); + await Assert.That(z).IsIn([true, false]); + } +} + +#endregion + +#region Edge Case: Instance data source at class level + +/// +/// Edge case documentation: When using CombinedDataSources at the class level with +/// an instance-requiring data source (like MethodDataSource pointing to an instance method), +/// the runtime check in CombinedDataSourcesAttribute.GetParameterValues() (lines 156-165) +/// will throw an InvalidOperationException with a clear, diagnostic error message: +/// +/// "Cannot use instance-based data source '{AttributeName}' on parameter '{name}' in class '{ClassName}'. +/// When [CombinedDataSources] is applied at the class level (constructor parameters), all data sources +/// must be static because no instance exists yet. Use static [MethodDataSource] or [Arguments] instead, +/// or move [CombinedDataSources] to the method level if you need instance-based data sources." +/// +/// This provides proper error handling with actionable guidance, replacing the confusing +/// "circular dependency" message that was previously shown due to the blanket IAccessesInstanceData check. +/// +public class EdgeCaseDocumentation; + +#endregion diff --git a/TUnit.TestProject/Bugs/3992/IAsyncInitializerDiscoveryTests.cs b/TUnit.TestProject/Bugs/3992/IAsyncInitializerDiscoveryTests.cs new file mode 100644 index 0000000000..b0c579b0ac --- /dev/null +++ b/TUnit.TestProject/Bugs/3992/IAsyncInitializerDiscoveryTests.cs @@ -0,0 +1,41 @@ +using TUnit.Core.Interfaces; +using TUnit.TestProject.Attributes; + +namespace TUnit.TestProject.Bugs._3992; + +/// +/// Regression test for issue #3992: IAsyncInitializer should not run during test discovery. +/// Verifies that IAsyncInitializer.InitializeAsync() is only called during test execution, +/// not during the discovery phase. +/// +[EngineTest(ExpectedResult.Pass)] +public class IAsyncInitializerDiscoveryTests +{ + private static int _initializationCount = 0; + + public class DataSourceWithAsyncInit : IAsyncInitializer + { + public bool IsInitialized { get; private set; } + + public Task InitializeAsync() + { + Interlocked.Increment(ref _initializationCount); + IsInitialized = true; + Console.WriteLine($"InitializeAsync called (count: {_initializationCount})"); + return Task.CompletedTask; + } + } + + [Test] + [ClassDataSource] + public async Task DataSource_InitializeAsync_OnlyCalledDuringExecution(DataSourceWithAsyncInit dataSource) + { + // Verify that the data source was initialized + await Assert.That(dataSource.IsInitialized).IsTrue(); + + // Verify that InitializeAsync was called (at least once during execution) + await Assert.That(_initializationCount).IsGreaterThan(0); + + Console.WriteLine($"Test execution confirmed: InitializeAsync was called {_initializationCount} time(s)"); + } +} diff --git a/TUnit.TestProject/Bugs/3992/InstanceMethodDataSourceWithAsyncInitializerTests.cs b/TUnit.TestProject/Bugs/3992/InstanceMethodDataSourceWithAsyncInitializerTests.cs new file mode 100644 index 0000000000..733b3b6b72 --- /dev/null +++ b/TUnit.TestProject/Bugs/3992/InstanceMethodDataSourceWithAsyncInitializerTests.cs @@ -0,0 +1,132 @@ +using System.Collections.Concurrent; +using TUnit.Core.Interfaces; +using TUnit.TestProject.Attributes; + +namespace TUnit.TestProject.Bugs._3992; + +/// +/// Regression test for issue #3992: IAsyncInitializer should not run during test discovery +/// when using InstanceMethodDataSource with ClassDataSource. +/// +/// This test replicates the user's scenario where: +/// 1. A ClassDataSource fixture implements IAsyncInitializer (e.g., starts Docker containers) +/// 2. An InstanceMethodDataSource returns predefined test case identifiers +/// 3. The fixture should NOT be initialized during discovery - only during execution +/// +/// The key insight is that test case IDENTIFIERS are known ahead of time (predefined), +/// but the actual fixture initialization (Docker containers, DB connections, etc.) +/// should only happen when tests actually execute. +/// +/// The bug caused Docker containers to start during test discovery (e.g., in IDE or --list-tests), +/// which was unexpected and resource-intensive. +/// +[EngineTest(ExpectedResult.Pass)] +public class InstanceMethodDataSourceWithAsyncInitializerTests +{ + private static int _initializationCount; + private static int _testExecutionCount; + private static readonly ConcurrentBag _observedInstanceIds = []; + + /// + /// Simulates a fixture like ClientServiceFixture that starts Docker containers. + /// Implements IAsyncInitializer (NOT IAsyncDiscoveryInitializer) because the user + /// does not want initialization during discovery. + /// + public class SimulatedContainerFixture : IAsyncInitializer + { + /// + /// Test case identifiers are PREDEFINED - they don't depend on initialization. + /// This allows discovery to enumerate test cases without initializing the fixture. + /// + private static readonly string[] PredefinedTestCases = ["TestCase1", "TestCase2", "TestCase3"]; + + /// + /// Unique identifier for this instance to verify sharing behavior. + /// + public Guid InstanceId { get; } = Guid.NewGuid(); + + public bool IsInitialized { get; private set; } + + /// + /// Returns predefined test case identifiers. These are available during discovery + /// WITHOUT requiring initialization. + /// + public IEnumerable GetTestCases() => PredefinedTestCases; + + public Task InitializeAsync() + { + Interlocked.Increment(ref _initializationCount); + Console.WriteLine($"[SimulatedContainerFixture] InitializeAsync called on instance {InstanceId} (count: {_initializationCount})"); + + // Simulate expensive container startup - this should NOT happen during discovery + IsInitialized = true; + + return Task.CompletedTask; + } + } + + [ClassDataSource(Shared = SharedType.PerClass)] + public required SimulatedContainerFixture Fixture { get; init; } + + /// + /// This property is accessed by InstanceMethodDataSource during discovery. + /// It returns predefined test case identifiers that don't require initialization. + /// The bug was that accessing this would trigger InitializeAsync() during discovery. + /// After the fix, InitializeAsync() should only be called during test execution. + /// + public IEnumerable TestExecutions => Fixture.GetTestCases(); + + [Test] + [InstanceMethodDataSource(nameof(TestExecutions))] + public async Task Test_WithInstanceMethodDataSource_DoesNotInitializeDuringDiscovery(string testCase) + { + Interlocked.Increment(ref _testExecutionCount); + + // Track this instance to verify sharing + _observedInstanceIds.Add(Fixture.InstanceId); + + // The fixture should be initialized by the time the test runs + await Assert.That(Fixture.IsInitialized) + .IsTrue() + .Because("the fixture should be initialized before test execution"); + + await Assert.That(testCase) + .IsNotNullOrEmpty() + .Because("the test case data should be available"); + + Console.WriteLine($"[Test] Executed with testCase='{testCase}', instanceId={Fixture.InstanceId}, " + + $"initCount={_initializationCount}, execCount={_testExecutionCount}"); + } + + [After(Class)] + public static async Task VerifyInitializationAndSharing() + { + // With SharedType.PerClass, the fixture should be initialized exactly ONCE + // during test execution, NOT during discovery. + // + // Before the fix: _initializationCount would be 2+ (discovery + execution) + // After the fix: _initializationCount should be exactly 1 (execution only) + + Console.WriteLine($"[After(Class)] Final counts - init: {_initializationCount}, exec: {_testExecutionCount}"); + Console.WriteLine($"[After(Class)] Unique instance IDs observed: {_observedInstanceIds.Distinct().Count()}"); + + await Assert.That(_initializationCount) + .IsEqualTo(1) + .Because("IAsyncInitializer should only be called once during execution, not during discovery"); + + await Assert.That(_testExecutionCount) + .IsEqualTo(3) + .Because("there should be 3 test executions (one per test case)"); + + // Verify that all tests used the SAME fixture instance (SharedType.PerClass) + var uniqueInstanceIds = _observedInstanceIds.Distinct().ToList(); + await Assert.That(uniqueInstanceIds) + .HasCount().EqualTo(1) + .Because("with SharedType.PerClass, all tests should share the same fixture instance"); + + // Reset for next run + _initializationCount = 0; + _testExecutionCount = 0; + _observedInstanceIds.Clear(); + } +} diff --git a/TUnit.TestProject/Bugs/3992/RuntimeInitializeTests.cs b/TUnit.TestProject/Bugs/3992/RuntimeInitializeTests.cs new file mode 100644 index 0000000000..1d86987a8a --- /dev/null +++ b/TUnit.TestProject/Bugs/3992/RuntimeInitializeTests.cs @@ -0,0 +1,56 @@ +using TUnit.Core.Interfaces; +using TUnit.TestProject.Attributes; + +namespace TUnit.TestProject.Bugs._3992; + +/// +/// Once this is discovered during test discovery, containers spin up +/// +[EngineTest(ExpectedResult.Pass)] +public sealed class RuntimeInitializeTests +{ + //Docker container + [ClassDataSource(Shared = SharedType.PerClass)] + public required DummyContainer Container { get; init; } + + [Before(Class)] + public static Task BeforeClass(ClassHookContext context) => NotInitialised(context.Tests); + + [After(TestDiscovery)] + public static Task AfterDiscovery(TestDiscoveryContext context) => NotInitialised(context.AllTests); + + public static async Task NotInitialised(IEnumerable tests) + { + var bugRecreations = tests.Select(x => x.Metadata.TestDetails.ClassInstance).OfType(); + + foreach (var bugRecreation in bugRecreations) + { + await Assert.That(bugRecreation.Container).IsNotNull(); + await Assert.That(DummyContainer.NumberOfInits).IsEqualTo(0); + } + } + + [Test, Arguments(1)] + public async Task Test(int value, CancellationToken token) + { + await Assert.That(value).IsNotDefault(); + await Assert.That(DummyContainer.NumberOfInits).IsEqualTo(1); + } + + public class DummyContainer : IAsyncInitializer, IAsyncDisposable + { + public Task InitializeAsync() + { + NumberOfInits++; + return Task.CompletedTask; + } + + public static int NumberOfInits { get; private set; } + + public ValueTask DisposeAsync() + { + return default; + } + } + +} diff --git a/TUnit.TestProject/Bugs/3992/RuntimeInitializeTests2.cs b/TUnit.TestProject/Bugs/3992/RuntimeInitializeTests2.cs new file mode 100644 index 0000000000..4edc097c47 --- /dev/null +++ b/TUnit.TestProject/Bugs/3992/RuntimeInitializeTests2.cs @@ -0,0 +1,56 @@ +using TUnit.Core.Interfaces; +using TUnit.TestProject.Attributes; + +namespace TUnit.TestProject.Bugs._3992; + +/// +/// Once this is discovered during test discovery, containers spin up +/// +[EngineTest(ExpectedResult.Pass)] +public sealed class DiscoveryInitializeTests +{ + //Docker container + [ClassDataSource(Shared = SharedType.PerClass)] + public required DummyContainer Container { get; init; } + + [Before(Class)] + public static Task BeforeClass(ClassHookContext context) => Initialised(context.Tests); + + [After(TestDiscovery)] + public static Task AfterDiscovery(TestDiscoveryContext context) => Initialised(context.AllTests); + + public static async Task Initialised(IEnumerable tests) + { + var bugRecreations = tests.Select(x => x.Metadata.TestDetails.ClassInstance).OfType(); + + foreach (var bugRecreation in bugRecreations) + { + await Assert.That(bugRecreation.Container).IsNotNull(); + await Assert.That(DummyContainer.NumberOfInits).IsEqualTo(1); + } + } + + [Test, Arguments(1)] + public async Task Test(int value, CancellationToken token) + { + await Assert.That(value).IsNotDefault(); + await Assert.That(DummyContainer.NumberOfInits).IsEqualTo(1); + } + + public class DummyContainer : IAsyncDiscoveryInitializer, IAsyncDisposable + { + public Task InitializeAsync() + { + NumberOfInits++; + return Task.CompletedTask; + } + + public static int NumberOfInits { get; private set; } + + public ValueTask DisposeAsync() + { + return default; + } + } + +} diff --git a/TUnit.TestProject/Bugs/3993/IAsyncInitializerTests.cs b/TUnit.TestProject/Bugs/3993/IAsyncInitializerTests.cs new file mode 100644 index 0000000000..58b07b362a --- /dev/null +++ b/TUnit.TestProject/Bugs/3993/IAsyncInitializerTests.cs @@ -0,0 +1,62 @@ +using System.Collections.Concurrent; +using TUnit.Core.Interfaces; +using TUnit.TestProject.Attributes; + +namespace TUnit.TestProject.Bugs._3993; + +/// +/// Regression test for issue #3993: IAsyncInitializer called 4 times instead of 3 +/// when using ClassDataSource with MethodDataSource that accesses instance properties. +/// +[EngineTest(ExpectedResult.Pass)] +public class IAsyncInitializerTests +{ + [ClassDataSource(Shared = SharedType.None)] + public required PerTestCaseSource PerTestCaseSource { get; init; } + + public IEnumerable PerTestCaseStrings() => PerTestCaseSource.MyString; + + [Test] + [MethodDataSource(nameof(PerTestCaseStrings))] + public async Task ForNTestCases_ShouldInitNTimes(string s) + { + await Assert.That(s).IsNotNull(); + + // Each of the 3 test cases should have its own isolated PerTestCaseSource instance + // Since Shared = SharedType.None, each test gets a new instance + // Therefore, InitializeAsync should be called exactly 3 times (once per test) + // NOT 4 times (which would include the discovery instance) + await Assert + .That(PerTestCaseSource.SetUps) + .IsEqualTo(3) + .Because("each of the 3 test cases should be wrapped in its own isolated test class, " + + "causing a call to async init, but no more than those three calls should be needed. " + + "The discovery-time instance should NOT be initialized."); + } + + [After(Class)] + public static void ResetCounter() + { + // Reset for next run + PerTestCaseSource.Reset(); + } +} + +public class PerTestCaseSource : IAsyncInitializer +{ + public string[] MyString = []; + private static int _setUps = 0; + public static int SetUps => _setUps; + + public Task InitializeAsync() + { + Interlocked.Increment(ref _setUps); + MyString = ["123", "321", "9210"]; + return Task.CompletedTask; + } + + public static void Reset() + { + _setUps = 0; + } +} diff --git a/TUnit.TestProject/Bugs/3997/Tests.cs b/TUnit.TestProject/Bugs/3997/Tests.cs new file mode 100644 index 0000000000..9fe62ef189 --- /dev/null +++ b/TUnit.TestProject/Bugs/3997/Tests.cs @@ -0,0 +1,58 @@ +using TUnit.Core; +using TUnit.Core.Interfaces; +using TUnit.TestProject.Attributes; + +namespace TUnit.TestProject.Bugs._3997; + +/// +/// Simulates a data source (like a Docker container) that needs initialization during test discovery +/// so that InstanceMethodDataSource can access its data when generating test cases. +/// +public class SimulatedContainer : IAsyncDiscoveryInitializer +{ + private readonly List _data = []; + public bool IsInitialized { get; private set; } + + public IReadOnlyList Data => _data; + + public Task InitializeAsync() + { + if (IsInitialized) + { + throw new InvalidOperationException("Container already initialized! InitializeAsync should only be called once."); + } + + // Simulate container startup and data population + _data.AddRange(["TestCase1", "TestCase2", "TestCase3"]); + IsInitialized = true; + return Task.CompletedTask; + } +} + +/// +/// Tests that IAsyncDiscoveryInitializer is called during test discovery, +/// allowing InstanceMethodDataSource to access initialized data. +/// +[EngineTest(ExpectedResult.Pass)] +public class DiscoveryInitializerTests +{ + [ClassDataSource(Shared = SharedType.PerClass)] + public required SimulatedContainer Container { get; init; } + + /// + /// This property provides test data from the initialized container. + /// The container MUST be initialized during discovery before this is evaluated. + /// + public IEnumerable TestCases => Container.Data; + + [Test] + [InstanceMethodDataSource(nameof(TestCases))] + public async Task TestWithContainerData(string testCase) + { + // Container should be initialized + await Assert.That(Container.IsInitialized).IsTrue(); + + // testCase should be one of the container's data items + await Assert.That(Container.Data).Contains(testCase); + } +} diff --git a/TUnit.TestProject/Bugs/4032/NestedAsyncInitializerTests.cs b/TUnit.TestProject/Bugs/4032/NestedAsyncInitializerTests.cs new file mode 100644 index 0000000000..f04d018ba2 --- /dev/null +++ b/TUnit.TestProject/Bugs/4032/NestedAsyncInitializerTests.cs @@ -0,0 +1,417 @@ +using TUnit.Core.Interfaces; +using TUnit.TestProject.Attributes; + +namespace TUnit.TestProject.Bugs._4032; + +/// +/// Regression tests for issue #4032: IAsyncInitializer lifecycle change broke complex test infrastructure orchestration. +/// The problem was that nested IAsyncInitializer dependencies weren't initialized before the parent's InitializeAsync was called. +/// This broke patterns like WebApplicationFactory + Testcontainers where the container must be initialized +/// before the WebApplicationFactory tries to access the container's connection string. +/// + +#region Mock Classes + +/// +/// Simulates a Testcontainers PostgreSQL container. +/// Must be initialized before any parent object tries to access GetConnectionString(). +/// +public class MockDatabaseContainer : IAsyncInitializer +{ + private static int _instanceCount; + private static int _initializeCount; + private static readonly List _initializationOrder = []; + + public static int InstanceCount => _instanceCount; + public static int InitializeCount => _initializeCount; + public static IReadOnlyList InitializationOrder => _initializationOrder; + + public int InstanceId { get; } + public bool IsInitialized { get; private set; } + public string? ConnectionString { get; private set; } + + public MockDatabaseContainer() + { + InstanceId = Interlocked.Increment(ref _instanceCount); + } + + public Task InitializeAsync() + { + Interlocked.Increment(ref _initializeCount); + IsInitialized = true; + ConnectionString = $"Host=localhost;Database=test_{InstanceId};User=test;Password=test"; + + lock (_initializationOrder) + { + _initializationOrder.Add(InstanceId); + } + + Console.WriteLine($"[MockDatabaseContainer #{InstanceId}] InitializeAsync called - ConnectionString: {ConnectionString}"); + return Task.CompletedTask; + } + + public string GetConnectionString() + { + if (!IsInitialized || ConnectionString == null) + { + throw new InvalidOperationException( + $"MockDatabaseContainer #{InstanceId} is not initialized! " + + "This indicates a bug in the IAsyncInitializer lifecycle ordering. " + + "Nested dependencies must be initialized before parent objects."); + } + + return ConnectionString; + } + + public static void Reset() + { + _instanceCount = 0; + _initializeCount = 0; + lock (_initializationOrder) + { + _initializationOrder.Clear(); + } + } +} + +/// +/// Simulates a WebApplicationFactory that depends on a database container. +/// The container MUST be initialized before this class's InitializeAsync is called, +/// because InitializeAsync accesses the container's connection string. +/// +public class MockWebApplicationFactory : IAsyncInitializer +{ + private static int _instanceCount; + private static int _initializeCount; + + public static int InstanceCount => _instanceCount; + public static int InitializeCount => _initializeCount; + + public int InstanceId { get; } + public bool IsInitialized { get; private set; } + public string? ConfiguredConnectionString { get; private set; } + + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required MockDatabaseContainer Database { get; init; } + + public MockWebApplicationFactory() + { + InstanceId = Interlocked.Increment(ref _instanceCount); + } + + public Task InitializeAsync() + { + Interlocked.Increment(ref _initializeCount); + + // This is the critical part: we need to access the container's connection string + // during initialization. If the container isn't initialized yet, this will throw. + ConfiguredConnectionString = Database.GetConnectionString(); + IsInitialized = true; + + Console.WriteLine($"[MockWebApplicationFactory #{InstanceId}] InitializeAsync called - Using connection: {ConfiguredConnectionString}"); + return Task.CompletedTask; + } + + public static void Reset() + { + _instanceCount = 0; + _initializeCount = 0; + } +} + +#endregion + +#region Tests + +/// +/// Tests that nested IAsyncInitializer dependencies are initialized before the parent's InitializeAsync. +/// This is the exact pattern from issue #4032. +/// +[NotInParallel] +[EngineTest(ExpectedResult.Pass)] +public class NestedAsyncInitializerOrderTests +{ + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required MockWebApplicationFactory Factory { get; init; } + + [Before(Class)] + public static void ResetCounters() + { + MockDatabaseContainer.Reset(); + MockWebApplicationFactory.Reset(); + } + + [Test] + public async Task Factory_ShouldBeInitialized() + { + // The factory should be initialized (which means the database must have been initialized first) + await Assert.That(Factory.IsInitialized).IsTrue() + .Because("the factory's InitializeAsync should have been called"); + } + + [Test] + public async Task Factory_ShouldHaveConfiguredConnectionString() + { + // The factory should have captured the connection string during initialization + await Assert.That(Factory.ConfiguredConnectionString).IsNotNull() + .Because("the factory should have accessed the database connection string during InitializeAsync"); + } + + [Test] + public async Task Database_ShouldBeInitializedBeforeFactory() + { + // The database should be initialized + await Assert.That(Factory.Database.IsInitialized).IsTrue() + .Because("the database container should be initialized before the factory accesses it"); + } + + [Test] + public async Task Database_InitializeCount_ShouldBeOne() + { + // The database should only be initialized once (shared per test session) + await Assert.That(MockDatabaseContainer.InitializeCount).IsEqualTo(1) + .Because("the database container is shared PerTestSession and should only be initialized once"); + } + + [Test] + public async Task Factory_InitializeCount_ShouldBeOne() + { + // The factory should only be initialized once (shared per test session) + await Assert.That(MockWebApplicationFactory.InitializeCount).IsEqualTo(1) + .Because("the factory is shared PerTestSession and should only be initialized once"); + } +} + +/// +/// Tests deep nesting (3 levels) of IAsyncInitializer dependencies. +/// Uses a shared static list to track initialization order reliably. +/// +public static class DeepNestingInitializationTracker +{ + private static readonly List _initializationOrder = []; + private static readonly object _lock = new(); + + public static void RecordInitialization(string name) + { + lock (_lock) + { + _initializationOrder.Add(name); + } + } + + public static IReadOnlyList GetOrder() + { + lock (_lock) + { + return _initializationOrder.ToList(); + } + } + + public static void Reset() + { + lock (_lock) + { + _initializationOrder.Clear(); + } + } +} + +public class DeepNestedDependency : IAsyncInitializer +{ + private static int _instanceCount; + private static int _initializeCount; + + public static int InstanceCount => _instanceCount; + public static int InitializeCount => _initializeCount; + + public int InstanceId { get; } + public bool IsInitialized { get; private set; } + public string? Value { get; private set; } + + public DeepNestedDependency() + { + InstanceId = Interlocked.Increment(ref _instanceCount); + } + + public Task InitializeAsync() + { + Interlocked.Increment(ref _initializeCount); + IsInitialized = true; + Value = $"DeepValue_{InstanceId}"; + DeepNestingInitializationTracker.RecordInitialization("Deep"); + Console.WriteLine($"[DeepNestedDependency #{InstanceId}] InitializeAsync called"); + return Task.CompletedTask; + } + + public string GetValue() + { + if (!IsInitialized || Value == null) + { + throw new InvalidOperationException($"DeepNestedDependency #{InstanceId} is not initialized!"); + } + + return Value; + } + + public static void Reset() + { + _instanceCount = 0; + _initializeCount = 0; + } +} + +public class MiddleDependency : IAsyncInitializer +{ + private static int _instanceCount; + private static int _initializeCount; + + public static int InstanceCount => _instanceCount; + public static int InitializeCount => _initializeCount; + + public int InstanceId { get; } + public bool IsInitialized { get; private set; } + public string? CombinedValue { get; private set; } + + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required DeepNestedDependency DeepDependency { get; init; } + + public MiddleDependency() + { + InstanceId = Interlocked.Increment(ref _instanceCount); + } + + public Task InitializeAsync() + { + Interlocked.Increment(ref _initializeCount); + + // Access the deep dependency - must be initialized first + CombinedValue = $"Middle_{InstanceId}_{DeepDependency.GetValue()}"; + IsInitialized = true; + DeepNestingInitializationTracker.RecordInitialization("Middle"); + Console.WriteLine($"[MiddleDependency #{InstanceId}] InitializeAsync called - CombinedValue: {CombinedValue}"); + return Task.CompletedTask; + } + + public string GetCombinedValue() + { + if (!IsInitialized || CombinedValue == null) + { + throw new InvalidOperationException($"MiddleDependency #{InstanceId} is not initialized!"); + } + + return CombinedValue; + } + + public static void Reset() + { + _instanceCount = 0; + _initializeCount = 0; + } +} + +public class TopLevelDependency : IAsyncInitializer +{ + private static int _instanceCount; + private static int _initializeCount; + + public static int InstanceCount => _instanceCount; + public static int InitializeCount => _initializeCount; + + public int InstanceId { get; } + public bool IsInitialized { get; private set; } + public string? FinalValue { get; private set; } + + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required MiddleDependency MiddleDependency { get; init; } + + public TopLevelDependency() + { + InstanceId = Interlocked.Increment(ref _instanceCount); + } + + public Task InitializeAsync() + { + Interlocked.Increment(ref _initializeCount); + + // Access the middle dependency - must be initialized first + FinalValue = $"Top_{InstanceId}_{MiddleDependency.GetCombinedValue()}"; + IsInitialized = true; + DeepNestingInitializationTracker.RecordInitialization("Top"); + Console.WriteLine($"[TopLevelDependency #{InstanceId}] InitializeAsync called - FinalValue: {FinalValue}"); + return Task.CompletedTask; + } + + public static void Reset() + { + _instanceCount = 0; + _initializeCount = 0; + } +} + +/// +/// Tests 3-level deep nesting of IAsyncInitializer dependencies. +/// Order must be: DeepNestedDependency -> MiddleDependency -> TopLevelDependency +/// +[NotInParallel] +[EngineTest(ExpectedResult.Pass)] +public class DeepNestedAsyncInitializerOrderTests +{ + [ClassDataSource(Shared = SharedType.PerTestSession)] + public required TopLevelDependency TopLevel { get; init; } + + [Before(Class)] + public static void ResetCounters() + { + DeepNestedDependency.Reset(); + MiddleDependency.Reset(); + TopLevelDependency.Reset(); + DeepNestingInitializationTracker.Reset(); + } + + [Test] + public async Task AllLevels_ShouldBeInitialized() + { + await Assert.That(TopLevel.IsInitialized).IsTrue(); + await Assert.That(TopLevel.MiddleDependency.IsInitialized).IsTrue(); + await Assert.That(TopLevel.MiddleDependency.DeepDependency.IsInitialized).IsTrue(); + } + + [Test] + public async Task InitializationOrder_ShouldBeDeepestFirst() + { + // Get the recorded initialization order + var order = DeepNestingInitializationTracker.GetOrder(); + + // Deep should be initialized first, then Middle, then Top + await Assert.That(order).HasCount().EqualTo(3) + .Because("there should be exactly 3 initializations"); + + await Assert.That(order[0]).IsEqualTo("Deep") + .Because("the deepest dependency should be initialized first"); + + await Assert.That(order[1]).IsEqualTo("Middle") + .Because("the middle dependency should be initialized second"); + + await Assert.That(order[2]).IsEqualTo("Top") + .Because("the top-level dependency should be initialized last"); + } + + [Test] + public async Task FinalValue_ShouldContainAllLevels() + { + // The final value should contain data from all three levels + await Assert.That(TopLevel.FinalValue).Contains("Top_"); + await Assert.That(TopLevel.FinalValue).Contains("Middle_"); + await Assert.That(TopLevel.FinalValue).Contains("DeepValue_"); + } + + [Test] + public async Task EachLevel_ShouldBeInitializedOnce() + { + await Assert.That(DeepNestedDependency.InitializeCount).IsEqualTo(1); + await Assert.That(MiddleDependency.InitializeCount).IsEqualTo(1); + await Assert.That(TopLevelDependency.InitializeCount).IsEqualTo(1); + } +} + +#endregion diff --git a/TUnit.TestProject/Bugs/_3882/CancellationAfterHooksTests.cs b/TUnit.TestProject/Bugs/_3882/CancellationAfterHooksTests.cs new file mode 100644 index 0000000000..1f1b19030b --- /dev/null +++ b/TUnit.TestProject/Bugs/_3882/CancellationAfterHooksTests.cs @@ -0,0 +1,174 @@ +using System.Diagnostics; + +namespace TUnit.TestProject.Bugs._3882; + +/// +/// Tests for issue #3882: After Test hook is not run when test is cancelled +/// https://github.com/thomhurst/TUnit/issues/3882 +/// +/// This test demonstrates that After hooks now execute even when tests are cancelled. +/// The Before hook starts a process, the test delays, and the After hook cleans up the process. +/// When cancelled via Test Explorer or timeout, the After hook should still execute. +/// +public class CancellationAfterHooksTests +{ + private static readonly string MarkerFileDirectory = Path.Combine(Path.GetTempPath(), "TUnit_3882_Tests"); + + [Before(Test)] + public async Task StartProcess(TestContext context) + { + // Create marker directory + Directory.CreateDirectory(MarkerFileDirectory); + + // Write marker to prove Before hook ran + var beforeMarker = Path.Combine(MarkerFileDirectory, $"before_{context.Metadata.TestName}.txt"); + await File.WriteAllTextAsync(beforeMarker, $"Before hook executed at {DateTime.Now:O}"); + } + + [Test] + [Timeout(2000)] // 2 second timeout to force cancellation + public async Task Test_ThatGets_Cancelled(CancellationToken cancellationToken) + { + // This test delays longer than the timeout, causing cancellation + await Task.Delay(10000, cancellationToken); + } + + [After(Test)] + public async Task StopProcess(TestContext context) + { + try + { + // Write marker to prove After hook ran EVEN ON CANCELLATION + var afterMarker = Path.Combine(MarkerFileDirectory, $"after_{context.Metadata.TestName}.txt"); + await File.WriteAllTextAsync(afterMarker, $"After hook executed at {DateTime.Now:O} - Outcome: {context.Execution.Result?.State}"); + } + catch (Exception ex) + { + // Don't let marker file creation failure prevent process cleanup + Console.WriteLine($"[AfterTest] Failed to write marker file: {ex.Message}"); + } + } +} + +/// +/// Tests for Session-level After hooks with cancellation +/// +public class SessionLevelCancellationTests +{ + private static readonly string SessionMarkerFile = Path.Combine(Path.GetTempPath(), "TUnit_3882_Session_After.txt"); + + [Before(TestSession)] + public static async Task SessionSetup(TestSessionContext context) + { + await File.WriteAllTextAsync( + Path.Combine(Path.GetTempPath(), "TUnit_3882_Session_Before.txt"), + $"Session Before hook executed at {DateTime.Now:O}"); + } + + [After(TestSession)] + public static async Task SessionCleanup(TestSessionContext context) + { + // This should run even if tests are cancelled + try + { + await File.WriteAllTextAsync( + SessionMarkerFile, + $"Session After hook executed at {DateTime.Now:O}"); + Console.WriteLine($"[AfterTestSession] Session After hook completed successfully"); + } + catch (Exception ex) + { + Console.WriteLine($"[AfterTestSession] Failed to write marker file: {ex.Message}"); + throw; // Re-throw to signal failure, but after logging + } + } + + [Test] + [Timeout(1000)] + public async Task SessionTest_ThatGets_Cancelled(CancellationToken cancellationToken) + { + await Task.Delay(5000, cancellationToken); + } +} + +/// +/// Tests for Assembly-level After hooks with cancellation +/// +public class AssemblyLevelCancellationTests +{ + private static readonly string AssemblyMarkerFile = Path.Combine(Path.GetTempPath(), "TUnit_3882_Assembly_After.txt"); + + [Before(Assembly)] + public static async Task AssemblySetup(AssemblyHookContext context) + { + await File.WriteAllTextAsync( + Path.Combine(Path.GetTempPath(), "TUnit_3882_Assembly_Before.txt"), + $"Assembly Before hook executed at {DateTime.Now:O}"); + } + + [After(Assembly)] + public static async Task AssemblyCleanup(AssemblyHookContext context) + { + // This should run even if tests are cancelled + try + { + await File.WriteAllTextAsync( + AssemblyMarkerFile, + $"Assembly After hook executed at {DateTime.Now:O}"); + Console.WriteLine($"[AfterAssembly] Assembly After hook completed successfully"); + } + catch (Exception ex) + { + Console.WriteLine($"[AfterAssembly] Failed to write marker file: {ex.Message}"); + throw; // Re-throw to signal failure, but after logging + } + } + + [Test] + [Timeout(1000)] + public async Task AssemblyTest_ThatGets_Cancelled(CancellationToken cancellationToken) + { + await Task.Delay(5000, cancellationToken); + } +} + +/// +/// Tests for Class-level After hooks with cancellation +/// +public class ClassLevelCancellationTests +{ + private static readonly string ClassMarkerFile = Path.Combine(Path.GetTempPath(), "TUnit_3882_Class_After.txt"); + + [Before(Class)] + public static async Task ClassSetup(ClassHookContext context) + { + await File.WriteAllTextAsync( + Path.Combine(Path.GetTempPath(), "TUnit_3882_Class_Before.txt"), + $"Class Before hook executed at {DateTime.Now:O}"); + } + + [After(Class)] + public static async Task ClassCleanup(ClassHookContext context) + { + // This should run even if tests are cancelled + try + { + await File.WriteAllTextAsync( + ClassMarkerFile, + $"Class After hook executed at {DateTime.Now:O}"); + Console.WriteLine($"[AfterClass] Class After hook completed successfully"); + } + catch (Exception ex) + { + Console.WriteLine($"[AfterClass] Failed to write marker file: {ex.Message}"); + throw; // Re-throw to signal failure, but after logging + } + } + + [Test] + [Timeout(1000)] + public async Task ClassTest_ThatGets_Cancelled(CancellationToken cancellationToken) + { + await Task.Delay(5000, cancellationToken); + } +} diff --git a/TUnit.TestProject/Bugs/_3882/ExternalCancellationTests.cs b/TUnit.TestProject/Bugs/_3882/ExternalCancellationTests.cs new file mode 100644 index 0000000000..509a8326aa --- /dev/null +++ b/TUnit.TestProject/Bugs/_3882/ExternalCancellationTests.cs @@ -0,0 +1,167 @@ +using System.Diagnostics; + +namespace TUnit.TestProject.Bugs._3882; + +[Timeout(300_000)] // Overall timeout for the test class to prevent indefinite hangs +public class ExternalCancellationTests +{ + private static readonly string MarkerFileDirectory = Path.Combine(Path.GetTempPath(), "TUnit_3882_External"); + + [Before(Test)] + public async Task StartProcess(TestContext context, CancellationToken cancellationToken) + { + // Create marker directory + Directory.CreateDirectory(MarkerFileDirectory); + + // Write marker to prove Before hook ran + var beforeMarker = Path.Combine(MarkerFileDirectory, $"before_{context.Metadata.TestName}.txt"); + await File.WriteAllTextAsync(beforeMarker, $"Before hook executed at {DateTime.Now:O}"); + } + + [Test] + // NO [Timeout] attribute - test runs indefinitely until cancelled externally + public async Task Test_ThatGets_Cancelled_Externally(CancellationToken cancellationToken) + { + // This test delays indefinitely, only stops via external cancellation + await Task.Delay(TimeSpan.FromHours(1), cancellationToken); + } + + [After(Test)] + public async Task StopProcess(TestContext context, CancellationToken cancellationToken) + { + try + { + // Write marker to prove After hook ran EVEN ON EXTERNAL CANCELLATION + var afterMarker = Path.Combine(MarkerFileDirectory, $"after_{context.Metadata.TestName}.txt"); + await File.WriteAllTextAsync(afterMarker, $"After hook executed at {DateTime.Now:O} - Outcome: {context.Execution.Result?.State}"); + } + catch (Exception ex) + { + // Don't let marker file creation failure prevent process cleanup + Console.WriteLine($"[AfterTest] Failed to write marker file: {ex.Message}"); + } + } +} + +/// +/// Tests for Session-level After hooks with external cancellation +/// +public class ExternalSessionLevelCancellationTests +{ + private static readonly string SessionMarkerFile = Path.Combine(Path.GetTempPath(), "TUnit_3882_External_Session_After.txt"); + + [Before(TestSession)] + public static async Task SessionSetup(TestSessionContext context) + { + await File.WriteAllTextAsync( + Path.Combine(Path.GetTempPath(), "TUnit_3882_External_Session_Before.txt"), + $"Session Before hook executed at {DateTime.Now:O}"); + } + + [After(TestSession)] + public static async Task SessionCleanup(TestSessionContext context) + { + // This should run even if tests are cancelled externally + try + { + await File.WriteAllTextAsync( + SessionMarkerFile, + $"Session After hook executed at {DateTime.Now:O}"); + Console.WriteLine($"[AfterTestSession] Session After hook completed successfully"); + } + catch (Exception ex) + { + Console.WriteLine($"[AfterTestSession] Failed to write marker file: {ex.Message}"); + throw; // Re-throw to signal failure, but after logging + } + } + + [Test] + // NO [Timeout] attribute - test runs indefinitely until cancelled externally + public async Task SessionTest_ThatGets_Cancelled_Externally(CancellationToken cancellationToken) + { + await Task.Delay(TimeSpan.FromHours(1), cancellationToken); + } +} + +/// +/// Tests for Assembly-level After hooks with external cancellation +/// +public class ExternalAssemblyLevelCancellationTests +{ + private static readonly string AssemblyMarkerFile = Path.Combine(Path.GetTempPath(), "TUnit_3882_External_Assembly_After.txt"); + + [Before(Assembly)] + public static async Task AssemblySetup(AssemblyHookContext context) + { + await File.WriteAllTextAsync( + Path.Combine(Path.GetTempPath(), "TUnit_3882_External_Assembly_Before.txt"), + $"Assembly Before hook executed at {DateTime.Now:O}"); + } + + [After(Assembly)] + public static async Task AssemblyCleanup(AssemblyHookContext context) + { + // This should run even if tests are cancelled externally + try + { + await File.WriteAllTextAsync( + AssemblyMarkerFile, + $"Assembly After hook executed at {DateTime.Now:O}"); + Console.WriteLine($"[AfterAssembly] Assembly After hook completed successfully"); + } + catch (Exception ex) + { + Console.WriteLine($"[AfterAssembly] Failed to write marker file: {ex.Message}"); + throw; // Re-throw to signal failure, but after logging + } + } + + [Test] + // NO [Timeout] attribute - test runs indefinitely until cancelled externally + public async Task AssemblyTest_ThatGets_Cancelled_Externally(CancellationToken cancellationToken) + { + await Task.Delay(TimeSpan.FromHours(1), cancellationToken); + } +} + +/// +/// Tests for Class-level After hooks with external cancellation +/// +public class ExternalClassLevelCancellationTests +{ + private static readonly string ClassMarkerFile = Path.Combine(Path.GetTempPath(), "TUnit_3882_External_Class_After.txt"); + + [Before(Class)] + public static async Task ClassSetup(ClassHookContext context) + { + await File.WriteAllTextAsync( + Path.Combine(Path.GetTempPath(), "TUnit_3882_External_Class_Before.txt"), + $"Class Before hook executed at {DateTime.Now:O}"); + } + + [After(Class)] + public static async Task ClassCleanup(ClassHookContext context) + { + // This should run even if tests are cancelled externally + try + { + await File.WriteAllTextAsync( + ClassMarkerFile, + $"Class After hook executed at {DateTime.Now:O}"); + Console.WriteLine($"[AfterClass] Class After hook completed successfully"); + } + catch (Exception ex) + { + Console.WriteLine($"[AfterClass] Failed to write marker file: {ex.Message}"); + throw; // Re-throw to signal failure, but after logging + } + } + + [Test] + // NO [Timeout] attribute - test runs indefinitely until cancelled externally + public async Task ClassTest_ThatGets_Cancelled_Externally(CancellationToken cancellationToken) + { + await Task.Delay(TimeSpan.FromHours(1), cancellationToken); + } +} diff --git a/TUnit.TestProject/ClassDataSourceSharedNoneRegressionTests.cs b/TUnit.TestProject/ClassDataSourceSharedNoneRegressionTests.cs new file mode 100644 index 0000000000..15df0b5c36 --- /dev/null +++ b/TUnit.TestProject/ClassDataSourceSharedNoneRegressionTests.cs @@ -0,0 +1,156 @@ +using TUnit.Core.Interfaces; +using TUnit.TestProject.Attributes; + +namespace TUnit.TestProject; + +/// +/// Regression tests for GitHub Issue #3855 +/// Verifies that ClassDataSource with SharedType.None constructs and initializes objects only once per test. +/// +[EngineTest(ExpectedResult.Pass)] +public class ClassDataSourceSharedNoneRegressionTests +{ + // Test helper class that tracks construction + public class ConstructionCounterClass + { + private static int _instanceCounter = 0; + public int InstanceNumber { get; } + + public ConstructionCounterClass() + { + InstanceNumber = Interlocked.Increment(ref _instanceCounter); + } + + public int Value { get; set; } = 42; + } + + // Test helper class that tracks initialization + public class AsyncInitializerCounterClass : IAsyncInitializer + { + private static int _initCounter = 0; + public int InitNumber { get; private set; } = -1; + + public Task InitializeAsync() + { + InitNumber = Interlocked.Increment(ref _initCounter); + return Task.CompletedTask; + } + + public int Value { get; set; } = 99; + } + + /// + /// Test that SharedType.None constructs the class data source exactly once per test. + /// Regression test for issue #3855 where it was being constructed twice. + /// Before the fix, InstanceNumber would be 2 (constructed twice). + /// After the fix, InstanceNumber should be 1 (constructed once). + /// + [Test] + [ClassDataSource(Shared = SharedType.None)] + public async Task SharedTypeNone_ConstructsOnlyOnce(ConstructionCounterClass instance) + { + // Instance should be constructed exactly once, so InstanceNumber should be 1 + // (not 2, which would indicate double construction) + await Assert.That(instance).IsNotNull(); + await Assert.That(instance.InstanceNumber).IsEqualTo(1); + await Assert.That(instance.Value).IsEqualTo(42); + } + + /// + /// Test that SharedType.None with IAsyncInitializer initializes exactly once per test. + /// Regression test for issue #3855 where InitializeAsync was being called twice. + /// Before the fix, InitNumber would be 2 (initialized twice). + /// After the fix, InitNumber should be 1 (initialized once). + /// + [Test] + [ClassDataSource(Shared = SharedType.None)] + public async Task SharedTypeNone_WithAsyncInitializer_InitializesOnlyOnce(AsyncInitializerCounterClass instance) + { + // Instance should be initialized exactly once, so InitNumber should be 1 + // (not 2, which would indicate double initialization) + await Assert.That(instance).IsNotNull(); + await Assert.That(instance.InitNumber).IsEqualTo(1); + await Assert.That(instance.Value).IsEqualTo(99); + } +} + +/// +/// Regression tests for GitHub Issue #3855 - Verify no instance leakage across tests. +/// Tests that multiple test methods in the same class each receive their own unique instance +/// with SharedType.None (no sharing or leakage across tests). +/// +public class ClassDataSourceSharedNoneNoLeakageTests +{ + // Test helper class that tracks instance IDs and mutation + public class UniqueInstanceClass + { + private static int _nextId = 0; + public int InstanceId { get; } + public int MutationValue { get; set; } + + public UniqueInstanceClass() + { + InstanceId = Interlocked.Increment(ref _nextId); + MutationValue = 0; // Initial value + } + + public static void ResetIdCounter() + { + _nextId = 0; + } + } + + [Before(TestSession)] + public static void ResetCounters() + { + UniqueInstanceClass.ResetIdCounter(); + } + + /// + /// First test - should get instance ID 1, mutate it, and not affect other tests + /// + [Test] + [ClassDataSource(Shared = SharedType.None)] + public async Task Test1_GetsUniqueInstance(UniqueInstanceClass instance) + { + // This is the first test, should have InstanceId = 1 + await Assert.That(instance.InstanceId).IsEqualTo(1); + await Assert.That(instance.MutationValue).IsEqualTo(0); + + // Mutate the instance + instance.MutationValue = 100; + } + + /// + /// Second test - should get instance ID 2, with fresh MutationValue = 0 + /// (proves no leakage from Test1) + /// + [Test] + [ClassDataSource(Shared = SharedType.None)] + public async Task Test2_GetsUniqueInstance(UniqueInstanceClass instance) + { + // This is the second test, should have InstanceId = 2 + await Assert.That(instance.InstanceId).IsEqualTo(2); + + // MutationValue should be 0 (not affected by Test1's mutation to 100) + await Assert.That(instance.MutationValue).IsEqualTo(0); + + // Mutate the instance + instance.MutationValue = 200; + } + + /// + /// Third test - should get instance ID 3, with fresh MutationValue = 0 + /// (proves no leakage from Test1 or Test2) + /// + [Test] + [ClassDataSource(Shared = SharedType.None)] + public async Task Test3_GetsUniqueInstance(UniqueInstanceClass instance) + { + // This is the third test, should have InstanceId = 3 + await Assert.That(instance.InstanceId).IsEqualTo(3); + + // MutationValue should be 0 (not affected by Test1 or Test2) + await Assert.That(instance.MutationValue).IsEqualTo(0); + } +} diff --git a/TUnit.TestProject/EventReceiverStageTests.cs b/TUnit.TestProject/EventReceiverStageTests.cs new file mode 100644 index 0000000000..9b5439c7ff --- /dev/null +++ b/TUnit.TestProject/EventReceiverStageTests.cs @@ -0,0 +1,311 @@ +using TUnit.Core.Enums; +using TUnit.Core.Interfaces; + +namespace TUnit.TestProject; + +// Shared execution order tracker +public static class EventReceiverStageTracker +{ + public static readonly List ExecutionOrder = []; +} + +// Early stage test start receiver +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] +public class EarlyTestStartReceiverAttribute : Attribute, ITestStartEventReceiver +{ + public int Order => 0; + +#if NET + public EventReceiverStage Stage => EventReceiverStage.Early; +#endif + + public ValueTask OnTestStart(TestContext context) + { + EventReceiverStageTracker.ExecutionOrder.Add("EarlyTestStart"); + return default; + } +} + +// Late stage test start receiver (explicit) +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] +public class LateTestStartReceiverAttribute : Attribute, ITestStartEventReceiver +{ + public int Order => 0; + +#if NET + public EventReceiverStage Stage => EventReceiverStage.Late; +#endif + + public ValueTask OnTestStart(TestContext context) + { + EventReceiverStageTracker.ExecutionOrder.Add("LateTestStart"); + return default; + } +} + +// Default test start receiver (should behave as Late) +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] +public class DefaultTestStartReceiverAttribute : Attribute, ITestStartEventReceiver +{ + public int Order => 0; + + // No Stage property override - uses default (Late) + + public ValueTask OnTestStart(TestContext context) + { + EventReceiverStageTracker.ExecutionOrder.Add("DefaultTestStart"); + return default; + } +} + +// Early stage test end receiver +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] +public class EarlyTestEndReceiverAttribute : Attribute, ITestEndEventReceiver +{ + public int Order => 0; + +#if NET + public EventReceiverStage Stage => EventReceiverStage.Early; +#endif + + public ValueTask OnTestEnd(TestContext context) + { + EventReceiverStageTracker.ExecutionOrder.Add("EarlyTestEnd"); + return default; + } +} + +// Late stage test end receiver (explicit) +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] +public class LateTestEndReceiverAttribute : Attribute, ITestEndEventReceiver +{ + public int Order => 0; + +#if NET + public EventReceiverStage Stage => EventReceiverStage.Late; +#endif + + public ValueTask OnTestEnd(TestContext context) + { + EventReceiverStageTracker.ExecutionOrder.Add("LateTestEnd"); + return default; + } +} + +// Default test end receiver (should behave as Late) +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] +public class DefaultTestEndReceiverAttribute : Attribute, ITestEndEventReceiver +{ + public int Order => 0; + + // No Stage property override - uses default (Late) + + public ValueTask OnTestEnd(TestContext context) + { + EventReceiverStageTracker.ExecutionOrder.Add("DefaultTestEnd"); + return default; + } +} + +// Test receivers with different Order values +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] +public class EarlyTestStartReceiver_Order1Attribute : Attribute, ITestStartEventReceiver +{ + public int Order => 1; + +#if NET + public EventReceiverStage Stage => EventReceiverStage.Early; +#endif + + public ValueTask OnTestStart(TestContext context) + { + EventReceiverStageTracker.ExecutionOrder.Add("EarlyTestStart_Order1"); + return default; + } +} + +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] +public class EarlyTestStartReceiver_Order2Attribute : Attribute, ITestStartEventReceiver +{ + public int Order => 2; + +#if NET + public EventReceiverStage Stage => EventReceiverStage.Early; +#endif + + public ValueTask OnTestStart(TestContext context) + { + EventReceiverStageTracker.ExecutionOrder.Add("EarlyTestStart_Order2"); + return default; + } +} + +public class EventReceiverStageTests +{ + [Before(Test)] + public void SetupTest() + { + EventReceiverStageTracker.ExecutionOrder.Clear(); + EventReceiverStageTracker.ExecutionOrder.Add("BeforeTest"); + } + + [After(Test)] + public void TeardownTest() + { + EventReceiverStageTracker.ExecutionOrder.Add("AfterTest"); + } + +#if NET + [Test] + [EarlyTestStartReceiver] + public async Task EarlyTestStartReceiver_RunsBeforeBeforeTestHook() + { + // Expected order: EarlyTestStart → BeforeTest → Test + await Assert.That(EventReceiverStageTracker.ExecutionOrder).HasCount().EqualTo(2); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[0]).IsEqualTo("EarlyTestStart"); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[1]).IsEqualTo("BeforeTest"); + } + + [Test] + [LateTestStartReceiver] + public async Task LateTestStartReceiver_RunsAfterBeforeTestHook() + { + // Expected order: BeforeTest → LateTestStart → Test + await Assert.That(EventReceiverStageTracker.ExecutionOrder).HasCount().EqualTo(2); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[0]).IsEqualTo("BeforeTest"); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[1]).IsEqualTo("LateTestStart"); + } + + [Test] + [DefaultTestStartReceiver] + public async Task DefaultTestStartReceiver_RunsAfterBeforeTestHook_AsLateStage() + { + // Expected order: BeforeTest → DefaultTestStart → Test + // Default should behave as Late stage + await Assert.That(EventReceiverStageTracker.ExecutionOrder).HasCount().EqualTo(2); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[0]).IsEqualTo("BeforeTest"); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[1]).IsEqualTo("DefaultTestStart"); + } + + [Test] + [EarlyTestEndReceiver] + public async Task EarlyTestEndReceiver_RunsBeforeAfterTestHook() + { + // Test runs, then in finally block: EarlyTestEnd → AfterTest + // We verify in AfterEvery hook + await Task.CompletedTask; + } + + [After(Test)] + public async Task VerifyEarlyTestEndReceiverOrder(TestContext context) + { + if (context.Metadata.DisplayName.Contains("EarlyTestEndReceiver_RunsBeforeAfterTestHook")) + { + // Expected order: BeforeTest → EarlyTestEnd → AfterTest + await Task.Delay(10); // Small delay to ensure all receivers have executed + await Assert.That(EventReceiverStageTracker.ExecutionOrder).HasCount().EqualTo(3); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[0]).IsEqualTo("BeforeTest"); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[1]).IsEqualTo("EarlyTestEnd"); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[2]).IsEqualTo("AfterTest"); + } + } + + [Test] + [LateTestEndReceiver] + public async Task LateTestEndReceiver_RunsAfterAfterTestHook() + { + // Test runs, then in finally block: AfterTest → LateTestEnd + await Task.CompletedTask; + } + + [After(Test)] + public async Task VerifyLateTestEndReceiverOrder(TestContext context) + { + if (context.Metadata.DisplayName.Contains("LateTestEndReceiver_RunsAfterAfterTestHook")) + { + // Expected order: BeforeTest → AfterTest → LateTestEnd + await Task.Delay(10); // Small delay to ensure all receivers have executed + await Assert.That(EventReceiverStageTracker.ExecutionOrder).HasCount().EqualTo(3); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[0]).IsEqualTo("BeforeTest"); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[1]).IsEqualTo("AfterTest"); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[2]).IsEqualTo("LateTestEnd"); + } + } + + [Test] + [DefaultTestEndReceiver] + public async Task DefaultTestEndReceiver_RunsAfterAfterTestHook_AsLateStage() + { + // Test runs, then in finally block: AfterTest → DefaultTestEnd + // Default should behave as Late stage + await Task.CompletedTask; + } + + [After(Test)] + public async Task VerifyDefaultTestEndReceiverOrder(TestContext context) + { + if (context.Metadata.DisplayName.Contains("DefaultTestEndReceiver_RunsAfterAfterTestHook_AsLateStage")) + { + // Expected order: BeforeTest → AfterTest → DefaultTestEnd + await Task.Delay(10); // Small delay to ensure all receivers have executed + await Assert.That(EventReceiverStageTracker.ExecutionOrder).HasCount().EqualTo(3); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[0]).IsEqualTo("BeforeTest"); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[1]).IsEqualTo("AfterTest"); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[2]).IsEqualTo("DefaultTestEnd"); + } + } + + [Test] + [EarlyTestStartReceiver_Order1] + [EarlyTestStartReceiver_Order2] + public async Task EarlyStageReceivers_RespectOrderProperty() + { + // Expected order: EarlyTestStart_Order1 → EarlyTestStart_Order2 → BeforeTest → Test + // Order property should still be respected within the same stage + await Assert.That(EventReceiverStageTracker.ExecutionOrder).HasCount().EqualTo(3); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[0]).IsEqualTo("EarlyTestStart_Order1"); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[1]).IsEqualTo("EarlyTestStart_Order2"); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[2]).IsEqualTo("BeforeTest"); + } + + [Test] + [EarlyTestStartReceiver] + [LateTestStartReceiver] + [EarlyTestEndReceiver] + [LateTestEndReceiver] + public async Task MixedStageReceivers_ExecuteInCorrectOrder() + { + // Expected order: EarlyTestStart → BeforeTest → LateTestStart → Test → EarlyTestEnd → AfterTest → LateTestEnd + await Assert.That(EventReceiverStageTracker.ExecutionOrder).HasCount().EqualTo(3); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[0]).IsEqualTo("EarlyTestStart"); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[1]).IsEqualTo("BeforeTest"); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[2]).IsEqualTo("LateTestStart"); + } + + [After(Test)] + public async Task VerifyMixedStageReceiversEndOrder(TestContext context) + { + if (context.Metadata.DisplayName.Contains("MixedStageReceivers_ExecuteInCorrectOrder")) + { + // Expected final order: EarlyTestStart → BeforeTest → LateTestStart → EarlyTestEnd → AfterTest → LateTestEnd + await Task.Delay(10); // Small delay to ensure all receivers have executed + await Assert.That(EventReceiverStageTracker.ExecutionOrder).HasCount().EqualTo(6); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[0]).IsEqualTo("EarlyTestStart"); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[1]).IsEqualTo("BeforeTest"); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[2]).IsEqualTo("LateTestStart"); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[3]).IsEqualTo("EarlyTestEnd"); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[4]).IsEqualTo("AfterTest"); + await Assert.That(EventReceiverStageTracker.ExecutionOrder[5]).IsEqualTo("LateTestEnd"); + } + } +#else + [Test] + public async Task OnNetStandard_AllReceiversBehaveLikeLateDueToNoDIM() + { + // On .NET Standard 2.0, the Stage property doesn't exist due to lack of default interface members + // All receivers should behave as Late stage (after hooks) + // This test is just a placeholder to document the behavior + await Assert.That(true).IsTrue(); + } +#endif +} diff --git a/TUnit.TestProject/TestBuildContextOutputCaptureTests.cs b/TUnit.TestProject/TestBuildContextOutputCaptureTests.cs new file mode 100644 index 0000000000..246e622391 --- /dev/null +++ b/TUnit.TestProject/TestBuildContextOutputCaptureTests.cs @@ -0,0 +1,191 @@ +using TUnit.Core; +using TUnit.Core.Interfaces; +using TUnit.TestProject.Attributes; + +namespace TUnit.TestProject; + +/// +/// Tests to verify that console output during test building (data source construction/initialization) +/// is properly captured and included in test results. +/// Issue #3833: https://github.com/thomhurst/TUnit/issues/3833 +/// +[NotInParallel] +[EngineTest(ExpectedResult.Pass)] +public class TestBuildContextOutputCaptureTests +{ + /// + /// Data source that writes to console in constructor + /// + public class DataSourceWithConstructorOutput + { + public string Value { get; } + + public DataSourceWithConstructorOutput() + { + Console.WriteLine("DataSource constructor: Creating instance"); + Console.WriteLine("DataSource constructor: Initializing value"); + Value = "TestValue"; + Console.WriteLine("DataSource constructor: Instance created successfully"); + } + } + + /// + /// Data source that writes to console in async initializer. + /// Uses IAsyncDiscoveryInitializer so it initializes during test discovery/building, + /// allowing the output to be captured in the test's build context. + /// Note: Regular IAsyncInitializer only runs during test execution (per issue #3992 fix). + /// + public class DataSourceWithAsyncInitOutput : IAsyncDiscoveryInitializer + { + public string Value { get; private set; } = "Uninitialized"; + + public DataSourceWithAsyncInitOutput() + { + Console.WriteLine("AsyncDataSource constructor: Creating instance"); + } + + public async Task InitializeAsync() + { + Console.WriteLine("AsyncDataSource.InitializeAsync: Starting initialization"); + await Task.Delay(10); // Simulate async work + Console.WriteLine("AsyncDataSource.InitializeAsync: Setting value"); + Value = "InitializedValue"; + Console.WriteLine("AsyncDataSource.InitializeAsync: Initialization complete"); + } + } + + /// + /// Data source that writes to error output + /// + public class DataSourceWithErrorOutput + { + public string Value { get; } + + public DataSourceWithErrorOutput() + { + Console.Error.WriteLine("DataSource error: This is an error message during construction"); + Value = "ErrorTestValue"; + } + } + + [Test] + [ClassDataSource] + public async Task Test_CapturesConstructorOutput_InTestResults(DataSourceWithConstructorOutput data) + { + // The constructor output should be captured during test building + // and included in the test's output + + // Get the test output + var output = TestContext.Current!.GetStandardOutput(); + + // Verify constructor output is present + await Assert.That(output).Contains("DataSource constructor: Creating instance"); + await Assert.That(output).Contains("DataSource constructor: Initializing value"); + await Assert.That(output).Contains("DataSource constructor: Instance created successfully"); + + // Verify the data source was created correctly + await Assert.That(data.Value).IsEqualTo("TestValue"); + } + + [Test] + [ClassDataSource] + public async Task Test_CapturesAsyncInitializerOutput_InTestResults(DataSourceWithAsyncInitOutput data) + { + // The InitializeAsync output should be captured during test building. + // Note: This uses IAsyncDiscoveryInitializer which runs during discovery. + // Regular IAsyncInitializer runs during execution only (per issue #3992 fix). + + // Get the test output + var output = TestContext.Current!.GetStandardOutput(); + + // Verify constructor and InitializeAsync output is present + await Assert.That(output).Contains("AsyncDataSource constructor: Creating instance"); + await Assert.That(output).Contains("AsyncDataSource.InitializeAsync: Starting initialization"); + await Assert.That(output).Contains("AsyncDataSource.InitializeAsync: Setting value"); + await Assert.That(output).Contains("AsyncDataSource.InitializeAsync: Initialization complete"); + + // Verify the data source was initialized correctly + await Assert.That(data.Value).IsEqualTo("InitializedValue"); + } + + [Test] + [ClassDataSource] + public async Task Test_CapturesErrorOutput_InTestResults(DataSourceWithErrorOutput data) + { + // Error output during construction should be captured + var errorOutput = TestContext.Current!.GetErrorOutput(); + + // Verify error output is present + await Assert.That(errorOutput).Contains("DataSource error: This is an error message during construction"); + + // Verify the data source was created correctly + await Assert.That(data.Value).IsEqualTo("ErrorTestValue"); + } + + /// + /// Shared data source (PerTestSession) - output should appear in all tests that use it + /// + public class SharedDataSource + { + public string Value { get; } + + public SharedDataSource() + { + Console.WriteLine("SharedDataSource constructor: This should appear in all tests using this data source"); + Value = "SharedValue"; + } + } + + [Test] + [ClassDataSource(Shared = SharedType.PerTestSession)] + public async Task Test_SharedDataSource_FirstTest(SharedDataSource data) + { + var output = TestContext.Current!.GetStandardOutput(); + + // Should contain the shared data source construction output + await Assert.That(output).Contains("SharedDataSource constructor:"); + await Assert.That(data.Value).IsEqualTo("SharedValue"); + } + + [Test] + [ClassDataSource(Shared = SharedType.PerTestSession)] + public async Task Test_SharedDataSource_SecondTest(SharedDataSource data) + { + // NOTE: For PerTestSession shared data sources, the constructor only runs once + // when the first test triggers creation. Subsequent tests use the already-created + // instance, so they won't have the constructor output in their build context. + // This is expected behavior. + + // Just verify the data source works + await Assert.That(data.Value).IsEqualTo("SharedValue"); + } + + [Test] + public async Task Test_ExecutionOutput_StillCaptured() + { + // Verify that execution-time output is still captured correctly + Console.WriteLine("This is execution-time output"); + + var output = TestContext.Current!.GetStandardOutput(); + await Assert.That(output).Contains("This is execution-time output"); + } + + [Test] + [ClassDataSource] + public async Task Test_BothBuildAndExecutionOutput_AreCombined(DataSourceWithConstructorOutput data) + { + // Write output during execution + Console.WriteLine("This is execution-time output"); + + var output = TestContext.Current!.GetStandardOutput(); + + // Should contain both build-time and execution-time output + await Assert.That(output).Contains("DataSource constructor: Creating instance"); + await Assert.That(output).Contains("This is execution-time output"); + + // Build-time output should come before execution-time output + var buildIndex = output.IndexOf("DataSource constructor: Creating instance", StringComparison.Ordinal); + var execIndex = output.IndexOf("This is execution-time output", StringComparison.Ordinal); + await Assert.That(buildIndex).IsLessThan(execIndex); + } +} diff --git a/TUnit.TestProject/TimeoutCancellationTokenTests.cs b/TUnit.TestProject/TimeoutCancellationTokenTests.cs index 3ed528ca58..d98717f7b8 100644 --- a/TUnit.TestProject/TimeoutCancellationTokenTests.cs +++ b/TUnit.TestProject/TimeoutCancellationTokenTests.cs @@ -75,3 +75,55 @@ public static IEnumerable DataSource() public class FiveSecondTimeout() : TimeoutAttribute(5_000); } + +// Positive test: Timeout does NOT fire for quick tests with data sources +[MethodDataSource(nameof(DataSource))] +[Timeout(30_000)] // Long timeout (30 seconds) +[EngineTest(ExpectedResult.Pass)] +[Category("Timeout Cancellation Token Tests")] +public class TimeoutDoesNotFireTests(int value) +{ + [Test] + public async Task QuickTestDoesNotTimeout(CancellationToken cancellationToken) + { + await Assert.That(value).IsEqualTo(1); + await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken); // Short delay + } + + public static IEnumerable DataSource() + { + yield return 1; + } +} + +// Test to verify cancellation token is actually triggered when timeout fires +[MethodDataSource(nameof(DataSource))] +[Timeout(5_000)] +[EngineTest(ExpectedResult.Failure)] +[Category("Timeout Cancellation Token Tests")] +public class CancellationTokenTriggeredTests(int value) +{ + [Test] + public async Task CancellationTokenIsTriggered(CancellationToken cancellationToken) + { + var fired = false; + cancellationToken.Register(() => fired = true); + + try + { + await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken); + } + catch (OperationCanceledException) + { + // Expected - timeout should trigger cancellation + } + + // Verify token was cancelled + await Assert.That(fired).IsTrue(); + } + + public static IEnumerable DataSource() + { + yield return 1; + } +} diff --git a/TUnit.UnitTests/PropertyDataSourceInjectionTests.cs b/TUnit.UnitTests/PropertyDataSourceInjectionTests.cs index 75dffd84e0..4bb4388a3c 100644 --- a/TUnit.UnitTests/PropertyDataSourceInjectionTests.cs +++ b/TUnit.UnitTests/PropertyDataSourceInjectionTests.cs @@ -227,3 +227,49 @@ public string GetDeepData() } } +// Regression test for Issue #3991: TestContext.Current is null during property injection +public class TestContextAvailabilityDuringPropertyInjectionTests +{ + [TestContextDataSource] + public required TestContextCapture? ContextCapture { get; set; } + + [Test] + public async Task PropertyInjection_CanAccessTestContext_DuringDiscoveryPhase() + { + // This test verifies that TestContext.Current is available during property injection + // in the discovery/registration phase (Issue #3991) + await Assert.That(ContextCapture).IsNotNull(); + await Assert.That(ContextCapture!.TestContextWasAvailable).IsTrue(); + await Assert.That(ContextCapture.CapturedTestContextId).IsNotNull(); + } +} + +// Data source that accesses TestContext.Current during initialization +// This reproduces the bug from Issue #3991 +public class TestContextDataSourceAttribute : AsyncDataSourceGeneratorAttribute +{ + protected override async IAsyncEnumerable>> GenerateDataSourcesAsync(DataGeneratorMetadata dataGeneratorMetadata) + { + yield return async () => + { + // This is where the bug occurs: TestContext.Current is null during discovery + var testContext = TestContext.Current; + + return new TestContextCapture + { + TestContextWasAvailable = testContext != null, + CapturedTestContextId = testContext?.Id, + CapturedCancellationToken = testContext?.CancellationToken ?? default + }; + }; + await Task.CompletedTask; + } +} + +public class TestContextCapture +{ + public bool TestContextWasAvailable { get; set; } + public string? CapturedTestContextId { get; set; } + public CancellationToken CapturedCancellationToken { get; set; } +} + diff --git a/docs/docs/advanced/extension-points.md b/docs/docs/advanced/extension-points.md index 8ad86faf7d..b4c9e0a1d3 100644 --- a/docs/docs/advanced/extension-points.md +++ b/docs/docs/advanced/extension-points.md @@ -262,9 +262,9 @@ public class TestReporterAttribute : Attribute, ITestStartEventReceiver, ITestEn { await ReportingService.ReportTestCompleted( context.GetDisplayName(), - context.Result?.State, - context.Result?.Duration, - context.Result?.Exception?.Message + context.Execution.Result?.State, + context.Execution.Result?.Duration, + context.Execution.Result?.Exception?.Message ); } } @@ -289,7 +289,7 @@ public class CustomEventReceiverAttribute : Attribute, ITestStartEventReceiver, public ValueTask OnTestEnd(TestContext context) { - Console.WriteLine($"Test ended: {context.GetDisplayName()} - {context.Result?.State}"); + Console.WriteLine($"Test ended: {context.GetDisplayName()} - {context.Execution.Result?.State}"); return default; } } diff --git a/docs/docs/advanced/performance-best-practices.md b/docs/docs/advanced/performance-best-practices.md index ff4645ae88..15034990fd 100644 --- a/docs/docs/advanced/performance-best-practices.md +++ b/docs/docs/advanced/performance-best-practices.md @@ -99,11 +99,26 @@ public class LazyDataProvider #### Configure Appropriate Parallelism ```csharp -// Set maximum parallel test execution -[assembly: MaxParallelTests(Environment.ProcessorCount)] +using TUnit.Core; +using TUnit.Core.Interfaces; -// Or use command line -// dotnet test --maximum-parallel-tests 8 +// Set maximum parallel test execution using an assembly-level parallel limiter +[assembly: ParallelLimiter] + +public class ProcessorCountLimit : IParallelLimit +{ + public int Limit => Environment.ProcessorCount; +} +``` + +Alternatively, use the command line flag: +```bash +dotnet test -- --maximum-parallel-tests 8 +``` + +Or set an environment variable: +```bash +export TUNIT_MAX_PARALLEL_TESTS=8 ``` #### Group Related Tests @@ -207,8 +222,8 @@ public class PerformantTests ```csharp // āŒ Bad: Expensive operation in assertion await Assert.That(await GetAllUsersFromDatabase()) - .HasCount() - .EqualTo(1000); + .Count() + .IsEqualTo(1000); // āœ… Good: Use efficient queries var userCount = await GetUserCountFromDatabase(); @@ -231,7 +246,7 @@ public async Task EfficientValidation() } // More expensive validations only if needed - await Assert.That(result.Items).HasCount().GreaterThan(0); + await Assert.That(result.Items).Count().IsGreaterThan(0); } ``` @@ -467,15 +482,20 @@ public class InMemoryDatabaseTests ```bash # Run fast unit tests first -dotnet test --filter "Category=Unit" --no-build +dotnet test --no-build -- --treenode-filter "/*/*/*/*[Category=Unit]" # Run slower integration tests separately -dotnet test --filter "Category=Integration" --no-build +dotnet test --no-build -- --treenode-filter "/*/*/*/*[Category=Integration]" # Run expensive E2E tests last -dotnet test --filter "Category=E2E" --no-build +dotnet test --no-build -- --treenode-filter "/*/*/*/*[Category=E2E]" ``` +> **Note**: With .NET 10 SDK or newer, you can use the simpler syntax: +> ```bash +> dotnet test --no-build --treenode-filter "/**[Category=Unit]" +> ``` + ### Use Test Result Caching ```xml @@ -533,16 +553,16 @@ public class PerformanceAwareExecutor : ITestExecutor public static void RecordTestMetrics() { var context = TestContext.Current; - if (context?.Result != null) + if (context?.Execution.Result != null) { TelemetryClient.TrackMetric( "TestDuration", - context.Result.Duration.TotalMilliseconds, + context.Execution.Result.Duration.TotalMilliseconds, new Dictionary { ["TestName"] = context.Metadata.TestName, ["TestClass"] = context.Metadata.TestDetails.TestClass, - ["Result"] = context.Result.State.ToString() + ["Result"] = context.Execution.Result.State.ToString() }); } } diff --git a/docs/docs/advanced/test-variants.md b/docs/docs/advanced/test-variants.md index 6e5d662808..9bb698c504 100644 --- a/docs/docs/advanced/test-variants.md +++ b/docs/docs/advanced/test-variants.md @@ -87,7 +87,7 @@ public class ShrinkOnFailureAttribute : Attribute, ITestEndEventReceiver public async ValueTask OnTestEnd(TestContext testContext) { // Only shrink if test failed and it's not already a shrink attempt - if (testContext.Result?.Status != TestStatus.Failed) + if (testContext.Execution.Result?.State != TestState.Failed) return; if (testContext.Relationship == TestRelationship.Derived) diff --git a/docs/docs/assertions/awaiting.md b/docs/docs/assertions/awaiting.md index 2185db045c..540a19102c 100644 --- a/docs/docs/assertions/awaiting.md +++ b/docs/docs/assertions/awaiting.md @@ -90,7 +90,7 @@ public async Task ComplexCollectionAssertions() // Assert multiple conditions on a collection await Assert.That(orders) - .HasCount().IsGreaterThan(0) + .Count().IsGreaterThan(0) .And.Contains(o => o.Status == OrderStatus.Completed) .And.DoesNotContain(o => o.Total < 0) .And.HasDistinctItems(); @@ -146,7 +146,7 @@ public async Task DetailedExceptionAssertions() var exception = await Assert.That(() => ParallelOperationAsync()) .Throws(); - await Assert.That(exception.InnerExceptions).HasCount(3); + await Assert.That(exception.InnerExceptions).Count().IsEqualTo(3); await Assert.That(exception.InnerExceptions).All(e => e is TaskCanceledException); } ``` @@ -169,7 +169,7 @@ public async Task CustomAssertionConditions() // Combine built-in and custom assertions await Assert.That(measurements) - .HasCount().GreaterThan(100) + .Count().IsGreaterThan(100) .And.All(m => m > 0) .And.Satisfies(IsNormallyDistributed, "Data should be normally distributed"); } @@ -242,7 +242,7 @@ public async Task StringPatternAssertions() .StartsWith("Report Generated:") .And.Contains("Total Items:") .And.DoesNotContain("null") - .And.HasLength().IsBetween(1000, 5000); + .And.Length().IsBetween(1000, 5000); } ``` @@ -299,7 +299,7 @@ public async Task PerformanceAssertions() await Assert.That(results.Max()) .IsLessThan(500); // No operation over 500ms - await Assert.That(results.Where(r => r > 200).HasCount()) + await Assert.That(results.Where(r => r > 200).Count()) .IsLessThan(5); // Less than 5% over 200ms } ``` diff --git a/docs/docs/assertions/collections.md b/docs/docs/assertions/collections.md index 52f72d5d27..db49665263 100644 --- a/docs/docs/assertions/collections.md +++ b/docs/docs/assertions/collections.md @@ -98,7 +98,7 @@ public async Task Collection_Does_Not_Contain_Matching() ## Count Assertions -### HasCount +### Count Tests that a collection has an exact count: @@ -108,7 +108,7 @@ public async Task Collection_Has_Count() { var numbers = new[] { 1, 2, 3, 4, 5 }; - await Assert.That(numbers).HasCount(5); + await Assert.That(numbers).Count().IsEqualTo(5); } ``` @@ -123,11 +123,11 @@ public async Task Count_With_Comparison() var numbers = new[] { 1, 2, 3, 4, 5 }; await Assert.That(numbers) - .HasCount().EqualTo(5); + .Count().IsEqualTo(5); await Assert.That(numbers) - .HasCount().GreaterThan(3) - .And.HasCount().LessThan(10); + .Count().IsGreaterThan(3) + .And.Count().IsLessThan(10); } ``` @@ -160,7 +160,7 @@ public async Task Collection_Is_Empty() var empty = new List(); await Assert.That(empty).IsEmpty(); - await Assert.That(empty).HasCount(0); + await Assert.That(empty).Count().IsEqualTo(0); } ``` @@ -638,7 +638,7 @@ public async Task Filter_And_Assert() var evens = numbers.Where(n => n % 2 == 0).ToArray(); await Assert.That(evens) - .HasCount(5) + .Count().IsEqualTo(5) .And.All(n => n % 2 == 0); } ``` @@ -659,7 +659,7 @@ public async Task LINQ_Query_Results() var adults = users.Where(u => u.Age >= 18).ToArray(); await Assert.That(adults) - .HasCount(3) + .Count().IsEqualTo(3) .And.All(u => u.Age >= 18); } ``` @@ -708,7 +708,7 @@ public async Task Map_And_Verify() var names = users.Select(u => u.Name).ToArray(); await Assert.That(names) - .HasCount(2) + .Count().IsEqualTo(2) .And.Contains("Alice") .And.Contains("Bob") .And.All(name => !string.IsNullOrEmpty(name)); @@ -745,12 +745,12 @@ public async Task Nested_Collections() new[] { 7, 8, 9 } }; - await Assert.That(matrix).HasCount(3); + await Assert.That(matrix).Count().IsEqualTo(3); await Assert.That(matrix).All(row => row.Length == 3); // Flatten and assert var flattened = matrix.SelectMany(x => x).ToArray(); - await Assert.That(flattened).HasCount(9); + await Assert.That(flattened).Count().IsEqualTo(9); } ``` @@ -768,7 +768,7 @@ public async Task Collection_Of_Collections() }; await Assert.That(groups) - .HasCount(3) + .Count().IsEqualTo(3) .And.All(group => group.Count > 0); } ``` @@ -783,7 +783,7 @@ public async Task Chained_Collection_Assertions() await Assert.That(numbers) .IsNotEmpty() - .And.HasCount(5) + .And.Count().IsEqualTo(5) .And.Contains(3) .And.DoesNotContain(10) .And.IsInOrder() @@ -807,7 +807,7 @@ public async Task Materialize_Before_Multiple_Assertions() // Materialize once to avoid re-execution var materialized = query.ToArray(); - await Assert.That(materialized).HasCount().GreaterThan(1000); + await Assert.That(materialized).Count().IsGreaterThan(1000); await Assert.That(materialized).Contains(100); await Assert.That(materialized).All(n => n % 2 == 0); } @@ -822,7 +822,7 @@ public async Task HashSet_Assertions() var set = new HashSet { 1, 2, 3, 4, 5 }; await Assert.That(set) - .HasCount(5) + .Count().IsEqualTo(5) .And.Contains(3) .And.HasDistinctItems(); } diff --git a/docs/docs/assertions/dictionaries.md b/docs/docs/assertions/dictionaries.md index 938a723f6e..24320dffa8 100644 --- a/docs/docs/assertions/dictionaries.md +++ b/docs/docs/assertions/dictionaries.md @@ -104,7 +104,7 @@ public async Task Dictionary_Count() ["c"] = 3 }; - await Assert.That(dict).HasCount(3); + await Assert.That(dict).Count().IsEqualTo(3); } ``` @@ -223,7 +223,7 @@ public async Task Lookup_Table() }; await Assert.That(statusCodes) - .HasCount(3) + .Count().IsEqualTo(3) .And.ContainsKey(200) .And.ContainsValue("OK"); } @@ -243,7 +243,7 @@ public async Task Cache_Contains_Entry() await Assert.That(cache) .ContainsKey("user:123") - .And.HasCount(2) + .And.Count().IsEqualTo(2) .And.IsNotEmpty(); } ``` @@ -306,7 +306,7 @@ public async Task Dictionary_Keys() var keys = dict.Keys; await Assert.That(keys) - .HasCount(3) + .Count().IsEqualTo(3) .And.Contains("a") .And.Contains("b") .And.Contains("c"); @@ -329,7 +329,7 @@ public async Task Dictionary_Values() var values = dict.Values; await Assert.That(values) - .HasCount(3) + .Count().IsEqualTo(3) .And.Contains(1) .And.Contains(2) .And.All(v => v > 0); @@ -376,7 +376,7 @@ public async Task Chained_Dictionary_Assertions() await Assert.That(dict) .IsNotEmpty() - .And.HasCount(3) + .And.Count().IsEqualTo(3) .And.ContainsKey("apple") .And.ContainsKey("banana") .And.ContainsValue(2) @@ -397,7 +397,7 @@ public async Task Concurrent_Dictionary() concurrent.TryAdd("b", 2); await Assert.That(concurrent) - .HasCount(2) + .Count().IsEqualTo(2) .And.ContainsKey("a"); } ``` @@ -412,7 +412,7 @@ public async Task ReadOnly_Dictionary() var readOnly = new ReadOnlyDictionary(dict); await Assert.That(readOnly) - .HasCount(1) + .Count().IsEqualTo(1) .And.ContainsKey("a"); } ``` diff --git a/docs/docs/assertions/equality-and-comparison.md b/docs/docs/assertions/equality-and-comparison.md index f20912b79f..f2b14bc868 100644 --- a/docs/docs/assertions/equality-and-comparison.md +++ b/docs/docs/assertions/equality-and-comparison.md @@ -54,7 +54,7 @@ public async Task Using_EqualTo_Alias() var numbers = new[] { 1, 2, 3 }; await Assert.That(numbers) - .HasCount().EqualTo(3) + .Count().IsEqualTo(3) .And.Contains(2); } ``` diff --git a/docs/docs/assertions/extensibility/extensibility-chaining-and-converting.md b/docs/docs/assertions/extensibility/extensibility-chaining-and-converting.md index befc48a581..59e748ad0f 100644 --- a/docs/docs/assertions/extensibility/extensibility-chaining-and-converting.md +++ b/docs/docs/assertions/extensibility/extensibility-chaining-and-converting.md @@ -185,4 +185,4 @@ public class HasDetailAssertion : Assertion TUnit includes several built-in examples of type conversion assertions: - `WhenParsedInto()` - Converts a string to a parsed type (e.g., `await Assert.That("123").WhenParsedInto().IsEqualTo(123)`) -- `IsTypeOf()` - Converts to a specific type (e.g., `await Assert.That(obj).IsTypeOf().HasLength(5)`) +- `IsTypeOf()` - Converts to a specific type (e.g., `await Assert.That(obj).IsTypeOf().Length().IsEqualTo(5)`) diff --git a/docs/docs/assertions/fsharp.md b/docs/docs/assertions/fsharp.md index 5b10b7ce84..6709cc0f10 100644 --- a/docs/docs/assertions/fsharp.md +++ b/docs/docs/assertions/fsharp.md @@ -14,9 +14,9 @@ So a test could look like: ```fsharp member this.CheckPositive() = async { - let result = 1 + 1 - do! check (Assert.That(result).IsPositive()) - } + let result = 1 + 1 + do! check (Assert.That(result).IsPositive()) +} ``` F# is a lot more strict with type resolution when it comes to extension methods and method overloads. Because of that you may need to annotate the type for the assertions. @@ -24,23 +24,42 @@ F# is a lot more strict with type resolution when it comes to extension methods For example, ```fsharp - [] - [] - member _.Test3() = async { - let value = "1" - do! check (Assert.That(value).IsEqualTo("1")) - } - - [] - [] - member _.Throws1() = async { - do! check (Assert.That(fun () -> task { return new string([||]) }).ThrowsException()) - } - - [] - [] - member _.Throws4() = async { - do! check (Assert.That(fun () -> Task.FromResult(true)).ThrowsNothing()) - } - -``` \ No newline at end of file +[] +[] +member _.Test3() = async { + let value = "1" + do! check (Assert.That(value).IsEqualTo("1")) +} + +[] +[] +member _.Throws1() = async { + do! check (Assert.That(fun () -> task { return new string([||]) }).ThrowsException()) +} + +[] +[] +member _.Throws4() = async { + do! check (Assert.That(fun () -> Task.FromResult(true)).ThrowsNothing()) +} +``` + +## taskAssert expression + +For Task-based tests you can also use the taskAssert computation expression to avoid wrapping every assertion in check. + +To use it, open the namespace: + +```fsharp +open TUnit.Assertions.FSharp.TaskAssert +``` + +Inside a taskAssert block you can directly do! the assertion: +```fsharp +[] +[] +member _.Test3() = taskAssert { + let value = "1" + do! Assert.That(value).IsEqualTo("1") +} +``` diff --git a/docs/docs/assertions/getting-started.md b/docs/docs/assertions/getting-started.md index 67c466360b..a078a9af9b 100644 --- a/docs/docs/assertions/getting-started.md +++ b/docs/docs/assertions/getting-started.md @@ -63,7 +63,7 @@ await Assert.That(input).IsNotEmpty(); ```csharp await Assert.That(numbers).Contains(42); -await Assert.That(items).HasCount(5); +await Assert.That(items).Count().IsEqualTo(5); await Assert.That(list).IsNotEmpty(); await Assert.That(values).All(x => x > 0); ``` @@ -99,8 +99,8 @@ Combine multiple assertions on the same value using `.And`: await Assert.That(username) .IsNotNull() .And.IsNotEmpty() - .And.HasLength().GreaterThan(3) - .And.HasLength().LessThan(20); + .And.Length().IsGreaterThan(3) + .And.Length().IsLessThan(20); ``` Use `.Or` when any condition can be true: @@ -153,7 +153,7 @@ Collections have rich assertion support: var numbers = new[] { 1, 2, 3, 4, 5 }; // Count and emptiness -await Assert.That(numbers).HasCount(5); +await Assert.That(numbers).Count().IsEqualTo(5); await Assert.That(numbers).IsNotEmpty(); // Membership @@ -397,7 +397,7 @@ Now that you understand the basics, explore specific assertion types: | Null/Default | `IsNull()`, `IsNotNull()`, `IsDefault()` | | Boolean | `IsTrue()`, `IsFalse()` | | Strings | `Contains()`, `StartsWith()`, `Matches()` | -| Collections | `Contains()`, `HasCount()`, `All()`, `Any()` | +| Collections | `Contains()`, `Count()`, `All()`, `Any()` | | Exceptions | `Throws()`, `ThrowsNothing()` | | Types | `IsTypeOf()`, `IsAssignableTo()` | | Async | `CompletesWithin()`, async exception testing | diff --git a/docs/docs/assertions/member-assertions.md b/docs/docs/assertions/member-assertions.md index ea0b7e4e4d..c0d8d7cec4 100644 --- a/docs/docs/assertions/member-assertions.md +++ b/docs/docs/assertions/member-assertions.md @@ -72,7 +72,7 @@ public async Task ComplexMemberAssertions() await Assert.That(team) .Member(t => t.Name, name => name.StartsWith("Team")) .And.Member(t => t.Members, members => members - .HasCount().IsGreaterThan(0) + .Count().IsGreaterThan(0) .And.All(m => m.IsActive) .And.Any(m => m.Role == "Lead")) .And.Member(t => t.CreatedDate, date => date @@ -134,7 +134,7 @@ public async Task NestedObjectAssertions() .And.Member(c => c.Address.City, city => city.IsEqualTo("Seattle")) .And.Member(c => c.Address.ZipCode, zip => zip.Matches(@"^\d{5}$")) .And.Member(c => c.Employees, employees => employees - .HasCount().IsBetween(100, 500) + .Count().IsBetween(100, 500) .And.All(e => e.Email.EndsWith("@techcorp.com"))); } ``` diff --git a/docs/docs/assertions/null-and-default.md b/docs/docs/assertions/null-and-default.md index c0570f6fbd..b2a241aa3a 100644 --- a/docs/docs/assertions/null-and-default.md +++ b/docs/docs/assertions/null-and-default.md @@ -69,7 +69,7 @@ public async Task Chained_After_Null_Check() await Assert.That(input) .IsNotNull() .And.IsNotEmpty() // Compiler knows input is not null - .And.HasLength().GreaterThan(5); + .And.Length().IsGreaterThan(5); } ``` diff --git a/docs/docs/assertions/regex-assertions.md b/docs/docs/assertions/regex-assertions.md index 6b2d9b1261..048aefc022 100644 --- a/docs/docs/assertions/regex-assertions.md +++ b/docs/docs/assertions/regex-assertions.md @@ -108,13 +108,13 @@ public async Task PositionAndLengthAssertions() // Assert that match has specific length await Assert.That(text) .Matches(pattern) - .HasLength(3); + .Length().IsEqualTo(3); // Combine with group assertions await Assert.That(text) .Matches(pattern) .AtIndex(12) - .And.HasLength(3); + .And.Length().IsEqualTo(3); } ``` @@ -149,7 +149,7 @@ public async Task ProductCodeValidation() .Matches(pattern) .And.Group("code", code => code.StartsWith("ABC")) .And.Group("price", price => price.Contains(".99")) - .And.Group("stock", stock => stock.HasLength(2)); + .And.Group("stock", stock => stock.Length().IsEqualTo(2)); } ``` @@ -259,9 +259,9 @@ public async Task CompleteEmailValidation() .And.Group("local", local => local.StartsWith("john")) .And.Group("subdomain", sub => sub.IsEqualTo("mail")) .And.Group("domain", domain => domain.IsEqualTo("example")) - .And.Group("tld", tld => tld.HasLength(3)) + .And.Group("tld", tld => tld.Length().IsEqualTo(3)) .And.AtIndex(0) - .And.HasLength(email.Length); + .And.Length().IsEqualTo(email.Length); } ``` diff --git a/docs/docs/assertions/string.md b/docs/docs/assertions/string.md index 7ffadc6bd6..6d9336f4a0 100644 --- a/docs/docs/assertions/string.md +++ b/docs/docs/assertions/string.md @@ -291,7 +291,7 @@ public async Task String_Is_Not_Empty() } ``` -### HasLength +### Length Tests that a string has a specific length: @@ -301,7 +301,7 @@ public async Task String_Has_Length() { var code = "ABC123"; - await Assert.That(code).HasLength(6); + await Assert.That(code).Length().IsEqualTo(6); } ``` @@ -314,12 +314,12 @@ public async Task Length_With_Comparison() var username = "alice"; await Assert.That(username) - .HasLength().GreaterThan(3) - .And.HasLength().LessThan(20); + .Length().IsGreaterThan(3) + .And.Length().IsLessThan(20); } ``` -Or more concisely: +Using `IsBetween`: ```csharp [Test] @@ -327,7 +327,7 @@ public async Task Length_Range() { var username = "alice"; - await Assert.That(username.Length).IsBetween(3, 20); + await Assert.That(username).Length().IsBetween(3, 20); } ``` @@ -459,8 +459,8 @@ public async Task Validate_Username() var username = "alice_123"; await Assert.That(username) - .HasLength().GreaterThanOrEqualTo(3) - .And.HasLength().LessThanOrEqualTo(20) + .Length().IsGreaterThanOrEqualTo(3) + .And.Length().IsLessThanOrEqualTo(20) .And.Matches(@"^[a-zA-Z0-9_]+$") .And.DoesNotContain(" "); } @@ -475,7 +475,7 @@ public async Task Validate_Password() var password = "SecureP@ss123"; await Assert.That(password) - .HasLength().GreaterThanOrEqualTo(8) + .Length().IsGreaterThanOrEqualTo(8) .And.Matches(@"[A-Z]") // Has uppercase .And.Matches(@"[a-z]") // Has lowercase .And.Matches(@"\d") // Has digit @@ -600,7 +600,7 @@ public async Task Chained_String_Assertions() .And.Contains("World") .And.StartsWith("Hello") .And.EndsWith("!") - .And.HasLength(13); + .And.Length().IsEqualTo(13); } ``` diff --git a/docs/docs/benchmarks/AsyncTests.md b/docs/docs/benchmarks/AsyncTests.md new file mode 100644 index 0000000000..3a0ac3d0da --- /dev/null +++ b/docs/docs/benchmarks/AsyncTests.md @@ -0,0 +1,75 @@ +--- +title: AsyncTests +description: Performance benchmark results for AsyncTests +sidebar_position: 2 +--- + +# AsyncTests Benchmark + +:::info Last Updated +This benchmark was automatically generated on **2025-12-10** from the latest CI run. + +**Environment:** Ubuntu Latest • .NET SDK 10.0.101 +::: + +## šŸ“Š Results + +| Framework | Version | Mean | Median | StdDev | +|-----------|---------|------|--------|--------| +| **TUnit** | 1.5.6 | 567.5 ms | 566.7 ms | 5.32 ms | +| NUnit | 4.4.0 | 736.1 ms | 735.9 ms | 6.54 ms | +| MSTest | 4.0.2 | 620.1 ms | 620.1 ms | 5.46 ms | +| xUnit3 | 3.2.1 | 710.4 ms | 713.3 ms | 8.37 ms | +| **TUnit (AOT)** | 1.5.6 | 122.6 ms | 122.5 ms | 0.37 ms | + +## šŸ“ˆ Visual Comparison + +```mermaid +%%{init: { + 'theme':'base', + 'themeVariables': { + 'primaryColor': '#2563eb', + 'primaryTextColor': '#ffffff', + 'primaryBorderColor': '#1e40af', + 'lineColor': '#6b7280', + 'secondaryColor': '#7c3aed', + 'tertiaryColor': '#dc2626', + 'background': '#ffffff', + 'mainBkg': '#2563eb', + 'secondBkg': '#7c3aed', + 'tertiaryBkg': '#dc2626', + 'clusterBkg': '#f3f4f6', + 'edgeLabelBackground': '#ffffff', + 'tertiaryTextColor': '#1f2937', + 'pie1': '#2563eb', + 'pie2': '#7c3aed', + 'pie3': '#dc2626', + 'pie4': '#f59e0b', + 'pie5': '#10b981', + 'pie6': '#06b6d4', + 'pie7': '#ec4899', + 'pie8': '#6366f1', + 'pie9': '#84cc16', + 'pie10': '#f97316', + 'pie11': '#14b8a6', + 'pie12': '#a855f7' + } +}}%% +xychart-beta + title "AsyncTests Performance Comparison" + x-axis ["TUnit", "NUnit", "MSTest", "xUnit3", "TUnit_AOT"] + y-axis "Time (ms)" 0 --> 884 + bar [567.5, 736.1, 620.1, 710.4, 122.6] +``` + +## šŸŽÆ Key Insights + +This benchmark compares TUnit's performance against NUnit, MSTest, xUnit3 using identical test scenarios. + +--- + +:::note Methodology +View the [benchmarks overview](/docs/benchmarks) for methodology details and environment information. +::: + +*Last generated: 2025-12-10T00:29:52.680Z* diff --git a/docs/docs/benchmarks/BuildTime.md b/docs/docs/benchmarks/BuildTime.md new file mode 100644 index 0000000000..851ef90716 --- /dev/null +++ b/docs/docs/benchmarks/BuildTime.md @@ -0,0 +1,72 @@ +--- +title: Build Performance +description: Compilation time benchmark results +sidebar_position: 8 +--- + +# Build Performance Benchmark + +:::info Last Updated +This benchmark was automatically generated on **2025-12-10** from the latest CI run. + +**Environment:** Ubuntu Latest • .NET SDK 10.0.101 +::: + +## šŸ“Š Results + +Compilation time comparison across frameworks: + +| Framework | Version | Mean | Median | StdDev | +|-----------|---------|------|--------|--------| +| **TUnit** | 1.5.6 | 2.039 s | 2.039 s | 0.0181 s | +| Build_NUnit | 4.4.0 | 1.630 s | 1.630 s | 0.0141 s | +| Build_MSTest | 4.0.2 | 1.720 s | 1.719 s | 0.0102 s | +| Build_xUnit3 | 3.2.1 | 1.627 s | 1.625 s | 0.0196 s | + +## šŸ“ˆ Visual Comparison + +```mermaid +%%{init: { + 'theme':'base', + 'themeVariables': { + 'primaryColor': '#2563eb', + 'primaryTextColor': '#ffffff', + 'primaryBorderColor': '#1e40af', + 'lineColor': '#6b7280', + 'secondaryColor': '#7c3aed', + 'tertiaryColor': '#dc2626', + 'background': '#ffffff', + 'mainBkg': '#2563eb', + 'secondBkg': '#7c3aed', + 'tertiaryBkg': '#dc2626', + 'clusterBkg': '#f3f4f6', + 'edgeLabelBackground': '#ffffff', + 'tertiaryTextColor': '#1f2937', + 'pie1': '#2563eb', + 'pie2': '#7c3aed', + 'pie3': '#dc2626', + 'pie4': '#f59e0b', + 'pie5': '#10b981', + 'pie6': '#06b6d4', + 'pie7': '#ec4899', + 'pie8': '#6366f1', + 'pie9': '#84cc16', + 'pie10': '#f97316', + 'pie11': '#14b8a6', + 'pie12': '#a855f7' + } +}}%% +xychart-beta + title "Build Time Comparison" + x-axis ["Build_TUnit", "Build_NUnit", "Build_MSTest", "Build_xUnit3"] + y-axis "Time (s)" 0 --> 3 + bar [2.039, 1.63, 1.72, 1.627] +``` + +--- + +:::note Methodology +View the [benchmarks overview](/docs/benchmarks) for methodology details and environment information. +::: + +*Last generated: 2025-12-10T00:29:52.682Z* diff --git a/docs/docs/benchmarks/DataDrivenTests.md b/docs/docs/benchmarks/DataDrivenTests.md new file mode 100644 index 0000000000..4d69d86387 --- /dev/null +++ b/docs/docs/benchmarks/DataDrivenTests.md @@ -0,0 +1,75 @@ +--- +title: DataDrivenTests +description: Performance benchmark results for DataDrivenTests +sidebar_position: 3 +--- + +# DataDrivenTests Benchmark + +:::info Last Updated +This benchmark was automatically generated on **2025-12-10** from the latest CI run. + +**Environment:** Ubuntu Latest • .NET SDK 10.0.101 +::: + +## šŸ“Š Results + +| Framework | Version | Mean | Median | StdDev | +|-----------|---------|------|--------|--------| +| **TUnit** | 1.5.6 | 511.20 ms | 508.62 ms | 7.821 ms | +| NUnit | 4.4.0 | 654.61 ms | 651.83 ms | 10.449 ms | +| MSTest | 4.0.2 | 612.10 ms | 611.26 ms | 7.900 ms | +| xUnit3 | 3.2.1 | 623.96 ms | 615.51 ms | 18.949 ms | +| **TUnit (AOT)** | 1.5.6 | 25.28 ms | 25.31 ms | 0.157 ms | + +## šŸ“ˆ Visual Comparison + +```mermaid +%%{init: { + 'theme':'base', + 'themeVariables': { + 'primaryColor': '#2563eb', + 'primaryTextColor': '#ffffff', + 'primaryBorderColor': '#1e40af', + 'lineColor': '#6b7280', + 'secondaryColor': '#7c3aed', + 'tertiaryColor': '#dc2626', + 'background': '#ffffff', + 'mainBkg': '#2563eb', + 'secondBkg': '#7c3aed', + 'tertiaryBkg': '#dc2626', + 'clusterBkg': '#f3f4f6', + 'edgeLabelBackground': '#ffffff', + 'tertiaryTextColor': '#1f2937', + 'pie1': '#2563eb', + 'pie2': '#7c3aed', + 'pie3': '#dc2626', + 'pie4': '#f59e0b', + 'pie5': '#10b981', + 'pie6': '#06b6d4', + 'pie7': '#ec4899', + 'pie8': '#6366f1', + 'pie9': '#84cc16', + 'pie10': '#f97316', + 'pie11': '#14b8a6', + 'pie12': '#a855f7' + } +}}%% +xychart-beta + title "DataDrivenTests Performance Comparison" + x-axis ["TUnit", "NUnit", "MSTest", "xUnit3", "TUnit_AOT"] + y-axis "Time (ms)" 0 --> 786 + bar [511.2, 654.61, 612.1, 623.96, 25.28] +``` + +## šŸŽÆ Key Insights + +This benchmark compares TUnit's performance against NUnit, MSTest, xUnit3 using identical test scenarios. + +--- + +:::note Methodology +View the [benchmarks overview](/docs/benchmarks) for methodology details and environment information. +::: + +*Last generated: 2025-12-10T00:29:52.680Z* diff --git a/docs/docs/benchmarks/MassiveParallelTests.md b/docs/docs/benchmarks/MassiveParallelTests.md new file mode 100644 index 0000000000..d44ebd2e19 --- /dev/null +++ b/docs/docs/benchmarks/MassiveParallelTests.md @@ -0,0 +1,75 @@ +--- +title: MassiveParallelTests +description: Performance benchmark results for MassiveParallelTests +sidebar_position: 4 +--- + +# MassiveParallelTests Benchmark + +:::info Last Updated +This benchmark was automatically generated on **2025-12-10** from the latest CI run. + +**Environment:** Ubuntu Latest • .NET SDK 10.0.101 +::: + +## šŸ“Š Results + +| Framework | Version | Mean | Median | StdDev | +|-----------|---------|------|--------|--------| +| **TUnit** | 1.5.6 | 602.8 ms | 602.8 ms | 5.09 ms | +| NUnit | 4.4.0 | 1,271.1 ms | 1,269.1 ms | 12.12 ms | +| MSTest | 4.0.2 | 2,955.5 ms | 2,953.9 ms | 6.21 ms | +| xUnit3 | 3.2.1 | 3,055.2 ms | 3,052.6 ms | 8.18 ms | +| **TUnit (AOT)** | 1.5.6 | 130.6 ms | 130.7 ms | 0.35 ms | + +## šŸ“ˆ Visual Comparison + +```mermaid +%%{init: { + 'theme':'base', + 'themeVariables': { + 'primaryColor': '#2563eb', + 'primaryTextColor': '#ffffff', + 'primaryBorderColor': '#1e40af', + 'lineColor': '#6b7280', + 'secondaryColor': '#7c3aed', + 'tertiaryColor': '#dc2626', + 'background': '#ffffff', + 'mainBkg': '#2563eb', + 'secondBkg': '#7c3aed', + 'tertiaryBkg': '#dc2626', + 'clusterBkg': '#f3f4f6', + 'edgeLabelBackground': '#ffffff', + 'tertiaryTextColor': '#1f2937', + 'pie1': '#2563eb', + 'pie2': '#7c3aed', + 'pie3': '#dc2626', + 'pie4': '#f59e0b', + 'pie5': '#10b981', + 'pie6': '#06b6d4', + 'pie7': '#ec4899', + 'pie8': '#6366f1', + 'pie9': '#84cc16', + 'pie10': '#f97316', + 'pie11': '#14b8a6', + 'pie12': '#a855f7' + } +}}%% +xychart-beta + title "MassiveParallelTests Performance Comparison" + x-axis ["TUnit", "NUnit", "MSTest", "xUnit3", "TUnit_AOT"] + y-axis "Time (ms)" 0 --> 3667 + bar [602.8, 1271.1, 2955.5, 3055.2, 130.6] +``` + +## šŸŽÆ Key Insights + +This benchmark compares TUnit's performance against NUnit, MSTest, xUnit3 using identical test scenarios. + +--- + +:::note Methodology +View the [benchmarks overview](/docs/benchmarks) for methodology details and environment information. +::: + +*Last generated: 2025-12-10T00:29:52.681Z* diff --git a/docs/docs/benchmarks/MatrixTests.md b/docs/docs/benchmarks/MatrixTests.md new file mode 100644 index 0000000000..6e23a71487 --- /dev/null +++ b/docs/docs/benchmarks/MatrixTests.md @@ -0,0 +1,75 @@ +--- +title: MatrixTests +description: Performance benchmark results for MatrixTests +sidebar_position: 5 +--- + +# MatrixTests Benchmark + +:::info Last Updated +This benchmark was automatically generated on **2025-12-10** from the latest CI run. + +**Environment:** Ubuntu Latest • .NET SDK 10.0.101 +::: + +## šŸ“Š Results + +| Framework | Version | Mean | Median | StdDev | +|-----------|---------|------|--------|--------| +| **TUnit** | 1.5.6 | 558.23 ms | 558.59 ms | 2.610 ms | +| NUnit | 4.4.0 | 1,630.43 ms | 1,629.39 ms | 6.793 ms | +| MSTest | 4.0.2 | 1,503.23 ms | 1,500.75 ms | 11.131 ms | +| xUnit3 | 3.2.1 | 1,590.84 ms | 1,590.40 ms | 10.966 ms | +| **TUnit (AOT)** | 1.5.6 | 77.58 ms | 77.58 ms | 0.269 ms | + +## šŸ“ˆ Visual Comparison + +```mermaid +%%{init: { + 'theme':'base', + 'themeVariables': { + 'primaryColor': '#2563eb', + 'primaryTextColor': '#ffffff', + 'primaryBorderColor': '#1e40af', + 'lineColor': '#6b7280', + 'secondaryColor': '#7c3aed', + 'tertiaryColor': '#dc2626', + 'background': '#ffffff', + 'mainBkg': '#2563eb', + 'secondBkg': '#7c3aed', + 'tertiaryBkg': '#dc2626', + 'clusterBkg': '#f3f4f6', + 'edgeLabelBackground': '#ffffff', + 'tertiaryTextColor': '#1f2937', + 'pie1': '#2563eb', + 'pie2': '#7c3aed', + 'pie3': '#dc2626', + 'pie4': '#f59e0b', + 'pie5': '#10b981', + 'pie6': '#06b6d4', + 'pie7': '#ec4899', + 'pie8': '#6366f1', + 'pie9': '#84cc16', + 'pie10': '#f97316', + 'pie11': '#14b8a6', + 'pie12': '#a855f7' + } +}}%% +xychart-beta + title "MatrixTests Performance Comparison" + x-axis ["TUnit", "NUnit", "MSTest", "xUnit3", "TUnit_AOT"] + y-axis "Time (ms)" 0 --> 1957 + bar [558.23, 1630.43, 1503.23, 1590.84, 77.58] +``` + +## šŸŽÆ Key Insights + +This benchmark compares TUnit's performance against NUnit, MSTest, xUnit3 using identical test scenarios. + +--- + +:::note Methodology +View the [benchmarks overview](/docs/benchmarks) for methodology details and environment information. +::: + +*Last generated: 2025-12-10T00:29:52.681Z* diff --git a/docs/docs/benchmarks/ScaleTests.md b/docs/docs/benchmarks/ScaleTests.md new file mode 100644 index 0000000000..63c8629157 --- /dev/null +++ b/docs/docs/benchmarks/ScaleTests.md @@ -0,0 +1,75 @@ +--- +title: ScaleTests +description: Performance benchmark results for ScaleTests +sidebar_position: 6 +--- + +# ScaleTests Benchmark + +:::info Last Updated +This benchmark was automatically generated on **2025-12-10** from the latest CI run. + +**Environment:** Ubuntu Latest • .NET SDK 10.0.101 +::: + +## šŸ“Š Results + +| Framework | Version | Mean | Median | StdDev | +|-----------|---------|------|--------|--------| +| **TUnit** | 1.5.6 | 518.74 ms | 518.92 ms | 2.688 ms | +| NUnit | 4.4.0 | 705.62 ms | 706.08 ms | 8.872 ms | +| MSTest | 4.0.2 | 673.90 ms | 672.14 ms | 9.096 ms | +| xUnit3 | 3.2.1 | 672.99 ms | 672.70 ms | 8.166 ms | +| **TUnit (AOT)** | 1.5.6 | 39.44 ms | 39.32 ms | 3.059 ms | + +## šŸ“ˆ Visual Comparison + +```mermaid +%%{init: { + 'theme':'base', + 'themeVariables': { + 'primaryColor': '#2563eb', + 'primaryTextColor': '#ffffff', + 'primaryBorderColor': '#1e40af', + 'lineColor': '#6b7280', + 'secondaryColor': '#7c3aed', + 'tertiaryColor': '#dc2626', + 'background': '#ffffff', + 'mainBkg': '#2563eb', + 'secondBkg': '#7c3aed', + 'tertiaryBkg': '#dc2626', + 'clusterBkg': '#f3f4f6', + 'edgeLabelBackground': '#ffffff', + 'tertiaryTextColor': '#1f2937', + 'pie1': '#2563eb', + 'pie2': '#7c3aed', + 'pie3': '#dc2626', + 'pie4': '#f59e0b', + 'pie5': '#10b981', + 'pie6': '#06b6d4', + 'pie7': '#ec4899', + 'pie8': '#6366f1', + 'pie9': '#84cc16', + 'pie10': '#f97316', + 'pie11': '#14b8a6', + 'pie12': '#a855f7' + } +}}%% +xychart-beta + title "ScaleTests Performance Comparison" + x-axis ["TUnit", "NUnit", "MSTest", "xUnit3", "TUnit_AOT"] + y-axis "Time (ms)" 0 --> 847 + bar [518.74, 705.62, 673.9, 672.99, 39.44] +``` + +## šŸŽÆ Key Insights + +This benchmark compares TUnit's performance against NUnit, MSTest, xUnit3 using identical test scenarios. + +--- + +:::note Methodology +View the [benchmarks overview](/docs/benchmarks) for methodology details and environment information. +::: + +*Last generated: 2025-12-10T00:29:52.681Z* diff --git a/docs/docs/benchmarks/SetupTeardownTests.md b/docs/docs/benchmarks/SetupTeardownTests.md new file mode 100644 index 0000000000..b2356fba2f --- /dev/null +++ b/docs/docs/benchmarks/SetupTeardownTests.md @@ -0,0 +1,75 @@ +--- +title: SetupTeardownTests +description: Performance benchmark results for SetupTeardownTests +sidebar_position: 7 +--- + +# SetupTeardownTests Benchmark + +:::info Last Updated +This benchmark was automatically generated on **2025-12-10** from the latest CI run. + +**Environment:** Ubuntu Latest • .NET SDK 10.0.101 +::: + +## šŸ“Š Results + +| Framework | Version | Mean | Median | StdDev | +|-----------|---------|------|--------|--------| +| **TUnit** | 1.5.6 | 555.3 ms | 554.9 ms | 12.00 ms | +| NUnit | 4.4.0 | 1,253.5 ms | 1,252.7 ms | 7.13 ms | +| MSTest | 4.0.2 | 1,091.2 ms | 1,091.0 ms | 9.74 ms | +| xUnit3 | 3.2.1 | 1,177.5 ms | 1,175.1 ms | 10.72 ms | +| **TUnit (AOT)** | 1.5.6 | NA | NA | NA | + +## šŸ“ˆ Visual Comparison + +```mermaid +%%{init: { + 'theme':'base', + 'themeVariables': { + 'primaryColor': '#2563eb', + 'primaryTextColor': '#ffffff', + 'primaryBorderColor': '#1e40af', + 'lineColor': '#6b7280', + 'secondaryColor': '#7c3aed', + 'tertiaryColor': '#dc2626', + 'background': '#ffffff', + 'mainBkg': '#2563eb', + 'secondBkg': '#7c3aed', + 'tertiaryBkg': '#dc2626', + 'clusterBkg': '#f3f4f6', + 'edgeLabelBackground': '#ffffff', + 'tertiaryTextColor': '#1f2937', + 'pie1': '#2563eb', + 'pie2': '#7c3aed', + 'pie3': '#dc2626', + 'pie4': '#f59e0b', + 'pie5': '#10b981', + 'pie6': '#06b6d4', + 'pie7': '#ec4899', + 'pie8': '#6366f1', + 'pie9': '#84cc16', + 'pie10': '#f97316', + 'pie11': '#14b8a6', + 'pie12': '#a855f7' + } +}}%% +xychart-beta + title "SetupTeardownTests Performance Comparison" + x-axis ["TUnit", "NUnit", "MSTest", "xUnit3", "TUnit_AOT"] + y-axis "Time (ms)" 0 --> 1505 + bar [555.3, 1253.5, 1091.2, 1177.5, 0] +``` + +## šŸŽÆ Key Insights + +This benchmark compares TUnit's performance against NUnit, MSTest, xUnit3 using identical test scenarios. + +--- + +:::note Methodology +View the [benchmarks overview](/docs/benchmarks) for methodology details and environment information. +::: + +*Last generated: 2025-12-10T00:29:52.682Z* diff --git a/docs/docs/benchmarks/index.md b/docs/docs/benchmarks/index.md index 2c5b59a725..23a17ed309 100644 --- a/docs/docs/benchmarks/index.md +++ b/docs/docs/benchmarks/index.md @@ -7,59 +7,26 @@ sidebar_position: 1 # Performance Benchmarks :::info Last Updated -These benchmarks were automatically generated on **2025-11-10** from the latest CI run. +These benchmarks were automatically generated on **2025-12-10** from the latest CI run. -**Environment:** Ubuntu Latest • .NET SDK 10.0.100-rc.2.25502.107 +**Environment:** Ubuntu Latest • .NET SDK 10.0.101 ::: -## šŸŽÆ Executive Summary +## šŸš€ Runtime Benchmarks -TUnit demonstrates significant performance advantages across all testing scenarios: +Click on any benchmark to view detailed results: -
+- [AsyncTests](AsyncTests) - Detailed performance analysis +- [DataDrivenTests](DataDrivenTests) - Detailed performance analysis +- [MassiveParallelTests](MassiveParallelTests) - Detailed performance analysis +- [MatrixTests](MatrixTests) - Detailed performance analysis +- [ScaleTests](ScaleTests) - Detailed performance analysis +- [SetupTeardownTests](SetupTeardownTests) - Detailed performance analysis -### Average Performance vs Other Frameworks -- **1.2x faster** than xUnit v3 -- **1.2x faster** than NUnit -- **1.0x faster** than MSTest +## šŸ”Ø Build Benchmarks -
- ---- - -## šŸš€ Runtime Performance - - -### results - -:::tip Native AOT Performance -TUnit with Native AOT compilation is **11.22x faster** than regular JIT! -::: - -**Performance:** **1.19x faster** than xUnit • **1.17x faster** than NUnit • **1.00x faster** than MSTest - -| Framework | Version | Mean | Median | StdDev | -|-----------|---------|------|--------|--------| -| šŸ† **TUnit** | 1.0.39 | 495.43 ms | 495.43 ms | 2.042 ms | -| NUnit | 4.4.0 | 578.77 ms | 580.83 ms | 7.718 ms | -| MSTest | 4.0.1 | 495.62 ms | 496.03 ms | 6.128 ms | -| xUnit3 | 3.2.0 | 591.61 ms | 591.28 ms | 9.984 ms | -| šŸ† **TUnit (AOT)** | 1.0.39 | 44.15 ms | 44.00 ms | 3.244 ms | - - ---- - -## šŸ”Ø Build Performance - -Compilation time comparison across frameworks: - -| Framework | Version | Mean | Median | StdDev | -|-----------|---------|------|--------|--------| -| šŸ† **TUnit** | 1.0.39 | 1.788 s | 1.789 s | 0.0352 s | -| Build_NUnit | 4.4.0 | 1.570 s | 1.571 s | 0.0178 s | -| Build_MSTest | 4.0.1 | 1.657 s | 1.661 s | 0.0240 s | -| Build_xUnit3 | 3.2.0 | 1.577 s | 1.575 s | 0.0136 s | +- [Build Performance](BuildTime) - Compilation time comparison --- @@ -70,10 +37,10 @@ These benchmarks compare TUnit against the most popular .NET testing frameworks: | Framework | Version Tested | |-----------|----------------| -| **TUnit** | 1.0.39 | -| **xUnit v3** | 3.2.0 | +| **TUnit** | 1.5.6 | +| **xUnit v3** | 3.2.1 | | **NUnit** | 4.4.0 | -| **MSTest** | 4.0.1 | +| **MSTest** | 4.0.2 | ### Test Scenarios @@ -81,17 +48,18 @@ The benchmarks measure real-world testing patterns: - **DataDrivenTests**: Parameterized tests with multiple data sources - **AsyncTests**: Realistic async/await patterns with I/O simulation -- **ScaleTests**: Large test suites (1000+ tests) measuring scalability +- **ScaleTests**: Large test suites (150+ tests) measuring scalability - **MatrixTests**: Combinatorial test generation and execution - **MassiveParallelTests**: Parallel execution stress tests +- **SetupTeardownTests**: Expensive test fixtures with setup/teardown overhead ### Environment - **OS**: Ubuntu Latest (GitHub Actions) -- **Runtime**: .NET 10.0.0 (10.0.0-rc.2.25502.107, 10.0.25.50307), X64 RyuJIT x86-64-v3 -- **SDK**: .NET SDK 10.0.100-rc.2.25502.107 +- **Runtime**: .NET 10.0.1 (10.0.1, 10.0.125.57005), X64 RyuJIT x86-64-v3 +- **SDK**: .NET SDK 10.0.101 - **Hardware**: GitHub Actions Standard Runner (Ubuntu) -- **Tool**: BenchmarkDotNet v0.15.6, Linux Ubuntu 24.04.3 LTS (Noble Numbat) +- **Tool**: BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.3 LTS (Noble Numbat) ### Why These Numbers Matter @@ -112,4 +80,4 @@ These benchmarks run automatically daily via [GitHub Actions](https://github.com Each benchmark runs multiple iterations with statistical analysis to ensure accuracy. Results may vary based on hardware and test characteristics. ::: -*Last generated: 2025-11-10T00:29:09.704Z* +*Last generated: 2025-12-10T00:29:52.683Z* diff --git a/docs/docs/examples/tunit-ci-pipeline.md b/docs/docs/examples/tunit-ci-pipeline.md index 07ea89d282..3d2bff9aab 100644 --- a/docs/docs/examples/tunit-ci-pipeline.md +++ b/docs/docs/examples/tunit-ci-pipeline.md @@ -331,7 +331,7 @@ test:integration: - build script: - dotnet test --configuration $BUILD_CONFIGURATION --no-build - --filter "Category=Integration" + -- --treenode-filter "/*/*/*/*[Category=Integration]" --report-trx --results-directory ./TestResults artifacts: when: always @@ -590,13 +590,18 @@ Run different test categories in separate jobs: ```yaml # Unit tests (fast) - name: Unit Tests - run: dotnet test --filter "Category=Unit" + run: dotnet test -- --treenode-filter "/*/*/*/*[Category=Unit]" # Integration tests (slower) - name: Integration Tests - run: dotnet test --filter "Category=Integration" + run: dotnet test -- --treenode-filter "/*/*/*/*[Category=Integration]" ``` +> **Note**: With .NET 10 SDK or newer, you can use the simpler syntax without the `--` separator: +> ```yaml +> run: dotnet test --treenode-filter "/**[Category=Unit]" +> ``` + ### Fail Fast in PRs Use fail-fast mode for quick feedback in pull requests: diff --git a/docs/docs/execution/engine-modes.md b/docs/docs/execution/engine-modes.md index 4a6eefbc0b..aa45293abd 100644 --- a/docs/docs/execution/engine-modes.md +++ b/docs/docs/execution/engine-modes.md @@ -107,7 +107,7 @@ Add this MSBuild property to your test project file (`.csproj`): ```xml - net9.0 + net10.0 false diff --git a/docs/docs/extensions/extensions.md b/docs/docs/extensions/extensions.md index fd78fc7d22..f8869d3589 100644 --- a/docs/docs/extensions/extensions.md +++ b/docs/docs/extensions/extensions.md @@ -108,7 +108,7 @@ Run your tests with the `--report-trx` flag: dotnet run --configuration Release --report-trx # Specify output location -dotnet run --configuration Release --report-trx --report-trx-filename ./reports/testresults.trx +dotnet run --configuration Release --results-directory ./reports --report-trx --report-trx-filename testresults.trx ``` **šŸ“š More Resources:** diff --git a/docs/docs/getting-started/writing-your-first-test.md b/docs/docs/getting-started/writing-your-first-test.md index ef503ae7d3..346ce02d08 100644 --- a/docs/docs/getting-started/writing-your-first-test.md +++ b/docs/docs/getting-started/writing-your-first-test.md @@ -188,7 +188,7 @@ public class StringTests // Assert await Assert.That(result).IsEqualTo("HELLO"); - await Assert.That(result).HasLength(5); + await Assert.That(result).Length().IsEqualTo(5); await Assert.That(result.StartsWith("HE")).IsTrue(); } } diff --git a/docs/docs/guides/best-practices.md b/docs/docs/guides/best-practices.md index 6bc1304297..b67eec7ed8 100644 --- a/docs/docs/guides/best-practices.md +++ b/docs/docs/guides/best-practices.md @@ -665,7 +665,7 @@ TUnit is designed for performance at scale. Follow these guidelines to keep your - Let tests run in parallel (it's fast!) - Only use `[NotInParallel]` when absolutely necessary -- Configure parallelism based on your CPU: `[assembly: MaxParallelTests(Environment.ProcessorCount)]` +- Configure parallelism using the `--maximum-parallel-tests` CLI flag or `[assembly: ParallelLimiter]` attribute - Avoid expensive setup in `[Before(Test)]` hooks - use class or assembly-level hooks for shared resources ### Avoid Slow Operations in Tests diff --git a/docs/docs/guides/cookbook.md b/docs/docs/guides/cookbook.md index 95b0eaad8d..6721c8df26 100644 --- a/docs/docs/guides/cookbook.md +++ b/docs/docs/guides/cookbook.md @@ -89,7 +89,7 @@ public class OrderServiceTests }); await Assert.That(order.Id).IsGreaterThan(0); - await Assert.That(order.Items).HasCount().EqualTo(1); + await Assert.That(order.Items).Count().IsEqualTo(1); } } ``` @@ -616,7 +616,7 @@ public class OrderRepositoryIntegrationTests var loaded = await repository.GetWithItemsAsync(order.Id); await Assert.That(loaded).IsNotNull(); - await Assert.That(loaded!.Items).HasCount().EqualTo(2); + await Assert.That(loaded!.Items).Count().IsEqualTo(2); } } ``` diff --git a/docs/docs/migration/nunit.md b/docs/docs/migration/nunit.md index ad278b1fcd..1e996268a1 100644 --- a/docs/docs/migration/nunit.md +++ b/docs/docs/migration/nunit.md @@ -137,7 +137,7 @@ await Assert.That(actual).IsEqualTo(expected); await Assert.That(value).IsTrue(); await Assert.That(value).IsNull(); await Assert.That(text).Contains("substring"); -await Assert.That(collection).HasCount().EqualTo(5); +await Assert.That(collection).Count().IsEqualTo(5); ``` ### Collection Assertions @@ -610,7 +610,7 @@ public async Task ComplexAssertions() await Assert.That(text).Matches(@"^Hello"); // Collection assertions - await Assert.That(list).HasCount().EqualTo(5); + await Assert.That(list).Count().IsEqualTo(5); await Assert.That(list).Contains(3); await Assert.That(list).AllSatisfy(x => x > 0); await Assert.That(list).IsInAscendingOrder(); diff --git a/docs/docs/migration/xunit.md b/docs/docs/migration/xunit.md index 97a96bb507..f452d62eb7 100644 --- a/docs/docs/migration/xunit.md +++ b/docs/docs/migration/xunit.md @@ -790,7 +790,7 @@ public async Task Collection_Assertions() await Assert.That(list).DoesNotContain(5); await Assert.That(Array.Empty()).IsEmpty(); await Assert.That(list).IsNotEmpty(); - await Assert.That(list).HasCount().EqualTo(3); + await Assert.That(list).Count().IsEqualTo(3); } ``` diff --git a/docs/docs/test-authoring/depends-on.md b/docs/docs/test-authoring/depends-on.md index 0a066bb90d..aa59ac5319 100644 --- a/docs/docs/test-authoring/depends-on.md +++ b/docs/docs/test-authoring/depends-on.md @@ -23,7 +23,7 @@ This takes a test name, which you can easily reference by using the `nameof(Test :::info -If you have multiple tests with the same name, but different parameter types, then you you must include the types of parameters in the `[DependsOn]` attribute too so TUnit can locate the correct test. +If you have multiple tests with the same name, but different parameter types, then you must include the types of parameters in the `[DependsOn]` attribute too so TUnit can locate the correct test. ::: diff --git a/docs/docs/test-authoring/skip.md b/docs/docs/test-authoring/skip.md index 58d9ef2aa1..02d8387d03 100644 --- a/docs/docs/test-authoring/skip.md +++ b/docs/docs/test-authoring/skip.md @@ -64,3 +64,111 @@ public class MyTestClass { } ``` + +## Dynamic Skipping at Runtime + +Sometimes you need to determine whether to skip a test at runtime based on conditions that aren't known until the test executes. For this, you can use the static `Skip.Test(reason)` method. + +### Skip.Test() + +The `Skip.Test(reason)` method allows you to dynamically skip a test from within the test method or hooks. When called, it throws a `SkipTestException` that the test framework catches and marks the test as skipped. + +```csharp +using TUnit.Core; + +namespace MyTestProject; + +public class MyTestClass +{ + [Test] + public async Task MyTest() + { + var apiAvailable = await CheckApiAvailability(); + + if (!apiAvailable) + { + Skip.Test("API is not available"); + } + + // Test continues only if API is available + await CallApi(); + } +} +``` + + +### Skipping from Hooks + +You can also use `Skip.Test()` in test hooks to skip tests based on setup conditions: + +```csharp +using TUnit.Core; + +namespace MyTestProject; + +public class MyTestClass +{ + [Before(Test)] + public void BeforeEachTest() + { + var databaseAvailable = CheckDatabaseConnection(); + + if (!databaseAvailable) + { + Skip.Test("Database is not available"); + } + } + + [Test] + public void Test1() + { + // This test will be skipped if database is unavailable + } + + [Test] + public void Test2() + { + // This test will also be skipped if database is unavailable + } +} +``` + +You can skip all tests in a class from a `Before(Class)` hook: + +```csharp +using TUnit.Core; + +namespace MyTestProject; + +public class MyTestClass +{ + [Before(Class)] + public static void BeforeAllTests() + { + var serviceAvailable = CheckExternalService(); + + if (!serviceAvailable) + { + Skip.Test("External service is not available"); + } + } + + [Test] + public void Test1() + { + // All tests in this class will be skipped if service is unavailable + } +} +``` + +### When to Use Dynamic Skipping + +Use `Skip.Test()` and its variants when: +- The skip condition depends on runtime state (external services, environment variables, etc.) +- You need to perform some logic or API calls to determine if a test should run +- The skip decision is based on test setup or initialization results + +Use `[Skip]` attribute when: +- The skip condition is known at compile time or discovery time +- You want to skip tests based on static configuration or platform checks +- You need custom skip logic in a reusable attribute diff --git a/docs/docs/test-lifecycle/cleanup.md b/docs/docs/test-lifecycle/cleanup.md index cb6366826f..6c66d55408 100644 --- a/docs/docs/test-lifecycle/cleanup.md +++ b/docs/docs/test-lifecycle/cleanup.md @@ -37,7 +37,7 @@ Hooks can optionally accept parameters for accessing context information and can public async Task Cleanup(TestContext context, CancellationToken cancellationToken) { // Access test results via context - if (context.Result?.Status == TestStatus.Failed) + if (context.Execution.Result?.State == TestState.Failed) { await CaptureScreenshot(cancellationToken); } diff --git a/docs/docs/test-lifecycle/event-subscribing.md b/docs/docs/test-lifecycle/event-subscribing.md index 7ba135e099..4588f92671 100644 --- a/docs/docs/test-lifecycle/event-subscribing.md +++ b/docs/docs/test-lifecycle/event-subscribing.md @@ -20,6 +20,98 @@ The interfaces they can implement are: This can be useful especially when generating data that you need to track and maybe dispose later. By hooking into these events, we can do things like track and dispose our objects when we need. +## Execution Stage Control + +> **Note**: This feature is available on .NET 8.0+ only due to default interface member requirements. + +For `ITestStartEventReceiver` and `ITestEndEventReceiver`, you can control when your event receivers execute relative to instance-level hooks (`[Before(Test)]` and `[After(Test)]`) by setting the `Stage` property. + +### EventReceiverStage Options + +- **`EventReceiverStage.Early`**: Executes before instance-level hooks + - Test start receivers run before `[Before(Test)]` hooks + - Test end receivers run before `[After(Test)]` hooks + +- **`EventReceiverStage.Late`** (default): Executes after instance-level hooks + - Test start receivers run after `[Before(Test)]` hooks + - Test end receivers run after `[After(Test)]` hooks + +### When to Use Early Stage + +Use `EventReceiverStage.Early` when your event receiver needs to: +- Initialize resources that instance-level hooks depend on +- Set up test context or environment before any test-specific setup runs +- Capture test state before any modifications from hooks + +### When to Use Late Stage + +Use `EventReceiverStage.Late` (the default) when your event receiver needs to: +- Access resources initialized by instance-level hooks +- Clean up after all test-specific teardown completes +- Log or report on final test state after all hooks have run + +### Example: Early Stage Event Receiver + +```csharp +public class DatabaseConnectionAttribute : Attribute, ITestStartEventReceiver +{ + private IDbConnection? _connection; + + // Execute before [Before(Test)] hooks so the connection is available to them + public EventReceiverStage Stage => EventReceiverStage.Early; + + public async ValueTask OnTestStart(TestContext context) + { + _connection = new SqlConnection(connectionString); + await _connection.OpenAsync(); + + // Store connection in test context for use by hooks and test + context.StateBag.GetOrAdd("DbConnection", _ => _connection); + } +} + +public class MyTests +{ + [Test] + [DatabaseConnection] // Runs BEFORE BeforeTest hook + public async Task TestWithDatabase() + { + // Database connection is already open and available + TestContext.Current!.StateBag.TryGetValue("DbConnection", out var connection); + // ... test logic + } + + [Before(Test)] + public void BeforeTest() + { + // Database connection is already available here + TestContext.Current!.StateBag.TryGetValue("DbConnection", out var connection); + // ... setup that needs the database + } +} +``` + +### Example: Late Stage Event Receiver (Default) + +```csharp +public class TestMetricsAttribute : Attribute, ITestEndEventReceiver +{ + // Late stage is the default, so this property is optional + public EventReceiverStage Stage => EventReceiverStage.Late; + + public async ValueTask OnTestEnd(TestContext context) + { + // This runs AFTER all [After(Test)] hooks have completed + // So we can capture the final test metrics including cleanup time + await LogMetrics(context); + } +} +``` + +### .NET Framework / .NET Standard 2.0 Behavior + +On older frameworks that don't support default interface members (.NET Framework, .NET Standard 2.0), the `Stage` property is not available. All event receivers will execute at the `Late` stage (after instance-level hooks), which matches the historical behavior. + Each attribute will be new'd up for each test, so you are able to store state within the fields of your attribute class. The `[ClassDataSource]` uses these events to do the following: @@ -36,7 +128,7 @@ public class DependencyInjectionClassConstructor : IClassConstructor, ITestEndEv { private readonly IServiceProvider _serviceProvider = CreateServiceProvider(); private AsyncServiceScope _scope; - + public Task Create([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type, ClassConstructorMetadata classConstructorMetadata) { _scope = _serviceProvider.CreateAsyncScope(); @@ -46,7 +138,7 @@ public class DependencyInjectionClassConstructor : IClassConstructor, ITestEndEv } public ValueTask OnTestEnd(TestContext testContext) - { + { return _scope.DisposeAsync(); } diff --git a/docs/docs/test-lifecycle/property-injection.md b/docs/docs/test-lifecycle/property-injection.md index 58470f0fa9..5f276dd4bb 100644 --- a/docs/docs/test-lifecycle/property-injection.md +++ b/docs/docs/test-lifecycle/property-injection.md @@ -8,11 +8,11 @@ The required keyword keeps your code clean and correct. If a property isn't pass ## AOT-Compatible Property Attributes Supported attributes for properties in AOT mode: -- **Argument** - Compile-time constant values -- **MethodDataSource** - Static method data sources -- **ClassDataSource** - Static class-based data sources +- **MethodDataSource** - Data sources via calling a method +- **`ClassDataSource`** - A data source that injects in T - **DataSourceGeneratorAttribute** - Source-generated data (first item only) -- **ClassDataSource** - Dependency injection with service provider + +For dependency injection with service providers, inherit from `DependencyInjectionDataSourceAttribute` to create custom attributes. See [Dependency Injection](./dependency-injection.md) documentation for details. The AOT system generates strongly-typed property setters at compile time, eliminating reflection overhead and ensuring Native AOT compatibility. @@ -55,10 +55,6 @@ namespace MyTestProject; public class PropertySetterTests { - // Compile-time constant injection - [Arguments("1")] - public required string Property1 { get; init; } - // Static method data source injection [MethodDataSource(nameof(GetMethodData))] public required string Property2 { get; init; } @@ -83,7 +79,7 @@ public class PropertySetterTests [DataSourceGeneratorTests.AutoFixtureGenerator] public required string Property7 { get; init; } - // Service provider dependency injection + // Async initialization example (IAsyncInitializer) [ClassDataSource] public required AsyncPropertyExample AsyncService { get; init; } @@ -91,7 +87,6 @@ public class PropertySetterTests public async Task Test() { // All properties are automatically initialized before this test runs - await Assert.That(Property1).IsEqualTo("1"); await Assert.That(Property2).IsNotNull(); await Assert.That(Property3).IsNotNull(); await Assert.That(AsyncService.IsInitialized).IsTrue(); @@ -99,12 +94,8 @@ public class PropertySetterTests Console.WriteLine($"Property7: {Property7}"); } - // Static data source method for Property2 - public static IEnumerable GetMethodData() - { - yield return "method_data_1"; - yield return "method_data_2"; - } + // Static data source method for Property2 - returns a single value for property injection + public static string GetMethodData() => "method_data_value"; } // Example model for ClassDataSource diff --git a/docs/docs/test-lifecycle/test-context.md b/docs/docs/test-lifecycle/test-context.md index 6884f43284..5292cce789 100644 --- a/docs/docs/test-lifecycle/test-context.md +++ b/docs/docs/test-lifecycle/test-context.md @@ -28,168 +28,11 @@ if (TestContext.Current?.Result?.State == TestState.Failed) } ``` -## Service Provider Integration +## Dependency Injection -`TestContext` provides access to dependency injection services through the `GetService()` and `GetRequiredService()` methods. This allows you to access registered services within your tests, hooks, and custom extensions. +**Note**: `TestContext` does NOT provide direct access to dependency injection services. The internal service provider in `TestContext` is exclusively for TUnit framework services and is not meant for user-provided dependencies. -### Accessing Services - -```csharp -[Test] -public async Task DatabaseTest() -{ - // Get an optional service (returns null if not registered) - var logger = TestContext.Current?.GetService>(); - logger?.LogInformation("Starting database test"); - - // Get a required service (throws if not registered) - var dbContext = TestContext.Current!.GetRequiredService(); - - // Use the service - var users = await dbContext.Users.ToListAsync(); - await Assert.That(users).IsNotEmpty(); -} -``` - -### Common Use Cases - -#### 1. Accessing Loggers - -```csharp -[Before(HookType.Test)] -public void LogTestStart() -{ - var logger = TestContext.Current?.GetService(); - logger?.LogInformation("Test {TestName} starting", - TestContext.Current?.Metadata.TestName); -} -``` - -#### 2. Working with Scoped Services - -```csharp -[Test] -public async Task ScopedServiceTest() -{ - // Each test gets its own scope, so scoped services are isolated - var service1 = TestContext.Current!.GetRequiredService(); - var service2 = TestContext.Current!.GetRequiredService(); - - // These will be the same instance within the test - await Assert.That(ReferenceEquals(service1, service2)).IsTrue(); -} -``` - -#### 3. Configuration Access - -```csharp -[Test] -public async Task ConfigurationTest() -{ - var configuration = TestContext.Current?.GetService(); - var apiKey = configuration?["ApiSettings:Key"]; - - await Assert.That(apiKey).IsNotNull(); -} -``` - -### Service Provider in Custom Extensions - -When implementing custom test executors or hook executors, you can use the service provider: - -```csharp -public class DatabaseTransactionExecutor : ITestExecutor -{ - public async Task ExecuteAsync(TestContext context, Func testBody) - { - // Get database context from DI - var dbContext = context.GetRequiredService(); - - using var transaction = await dbContext.Database.BeginTransactionAsync(); - - try - { - await testBody(); - await transaction.RollbackAsync(); // Keep tests isolated - } - catch - { - await transaction.RollbackAsync(); - throw; - } - } -} -``` - -### Integration with Test Lifecycle - -The service provider is available throughout the test lifecycle: - -```csharp -public class ServiceIntegrationTests -{ - [Before(HookType.Class)] - public static async Task ClassSetup() - { - // Services available in class-level hooks via the hook context - var context = ClassHookContext.Current; - var cache = context?.GetService(); - cache?.Set("test-data", await LoadTestData()); - } - - [Before(HookType.Test)] - public async Task TestSetup() - { - // Services available in test-level hooks - var cache = TestContext.Current?.GetService(); - var testData = cache?.Get("test-data"); - } - - [Test] - public async Task ActualTest() - { - // Services available in test methods - var service = TestContext.Current!.GetRequiredService(); - var result = await service.PerformOperation(); - await Assert.That(result).IsNotNull(); - } -} -``` - -### Best Practices - -1. **Use GetRequiredService for Essential Services** - ```csharp - // Good - Fails fast if service is missing - var critical = TestContext.Current!.GetRequiredService(); - - // Less ideal - Might hide configuration issues - var critical = TestContext.Current?.GetService() - ?? throw new InvalidOperationException("Service not found"); - ``` - -2. **Null Check When Using GetService** - ```csharp - var optional = TestContext.Current?.GetService(); - if (optional != null) - { - await optional.DoSomething(); - } - ``` - -3. **Consider Service Lifetime** - ```csharp - // Singleton services persist across tests - var singleton = TestContext.Current?.GetService(); - - // Scoped services are unique per test - var scoped = TestContext.Current?.GetService(); - - // Transient services are created each time - var transient1 = TestContext.Current?.GetService(); - var transient2 = TestContext.Current?.GetService(); - // transient1 and transient2 are different instances - ``` +If you need dependency injection in your tests, use the `DependencyInjectionDataSourceAttribute` helper class to set up your own DI container. See the [Dependency Injection guide](./dependency-injection.md) for complete details and examples. ## TestBuilderContext diff --git a/docs/docs/troubleshooting.md b/docs/docs/troubleshooting.md index b0f44ddad1..4dfa0bb1ab 100644 --- a/docs/docs/troubleshooting.md +++ b/docs/docs/troubleshooting.md @@ -463,7 +463,7 @@ await Assert.That(actual).IsEquivalentTo(expected, CollectionOrdering.Matching); Or assert on elements individually: ```csharp -await Assert.That(actual).HasCount().EqualTo(expected.Length); +await Assert.That(actual).Count().IsEqualTo(expected.Length); for (int i = 0; i < expected.Length; i++) { await Assert.That(actual[i]).IsEqualTo(expected[i]); @@ -483,7 +483,7 @@ var expected = new[] await Assert.That(actual).IsEquivalentTo(expected); // More reliable - assert on properties -await Assert.That(actual).HasCount().EqualTo(2); +await Assert.That(actual).Count().IsEqualTo(2); await Assert.That(actual[0].Name).IsEqualTo("Alice"); await Assert.That(actual[1].Name).IsEqualTo("Bob"); @@ -502,7 +502,7 @@ var actual = new[] { (1, "a"), (2, "b") }; // await Assert.That(actual).IsEquivalentTo(expected); // Workaround - assert individual elements -await Assert.That(actual).HasCount().EqualTo(2); +await Assert.That(actual).Count().IsEqualTo(2); await Assert.That(actual[0]).IsEqualTo((1, "a")); await Assert.That(actual[1]).IsEqualTo((2, "b")); ``` @@ -516,7 +516,7 @@ var list = new List { 1, 2, 3 }; await Assert.That(list).IsEquivalentTo(new[] { 1, 2, 3 }); // Check specific properties -await Assert.That(list).HasCount().EqualTo(3); +await Assert.That(list).Count().IsEqualTo(3); await Assert.That(list).Contains(2); await Assert.That(list).DoesNotContain(5); ``` diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index 0b75b8f24b..5c2a8f85c9 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -31,6 +31,10 @@ const config: Config = { locales: ['en'], }, + plugins: [ + 'docusaurus-plugin-llms', + ], + presets: [ [ 'classic', @@ -55,6 +59,11 @@ const config: Config = { ], ], + markdown: { + mermaid: true, + }, + themes: ['@docusaurus/theme-mermaid'], + themeConfig: { // Replace with your project's social card algolia: { @@ -100,6 +109,11 @@ const config: Config = { label: 'GitHub', position: 'right', }, + { + href: 'https://github.com/sponsors/thomhurst', + label: 'ā¤ļø Sponsor', + position: 'right', + }, ], }, footer: { diff --git a/docs/package.json b/docs/package.json index 15e14f9e45..ae51cac527 100644 --- a/docs/package.json +++ b/docs/package.json @@ -17,16 +17,18 @@ "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/preset-classic": "3.9.2", - "@mdx-js/react": "^3.0.0", + "@docusaurus/theme-mermaid": "^3.9.2", + "@mdx-js/react": "^3.1.1", "clsx": "^2.1.1", "prism-react-renderer": "^2.4.0", - "react": "^19.0.0", + "react": "^19.2.0", "react-dom": "^19.0.0" }, "devDependencies": { "@docusaurus/module-type-aliases": "3.9.2", "@docusaurus/tsconfig": "3.9.2", "@docusaurus/types": "3.9.2", + "docusaurus-plugin-llms": "^0.2.2", "typescript": "~5.9.0" }, "browserslist": { diff --git a/docs/sidebars.ts b/docs/sidebars.ts index fd63fab784..5e446d9f36 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -290,8 +290,10 @@ const sidebars: SidebarsConfig = { label: 'Performance & Benchmarks', collapsed: false, items: [ - 'benchmarks/index', - 'benchmarks/methodology', + { + type: 'autogenerated', + dirName: 'benchmarks', + }, ], }, ], diff --git a/docs/src/css/benchmark-charts.css b/docs/src/css/benchmark-charts.css new file mode 100644 index 0000000000..cf992fae08 --- /dev/null +++ b/docs/src/css/benchmark-charts.css @@ -0,0 +1,69 @@ +/* Enhanced styling for Mermaid benchmark charts */ + +/* Override Mermaid xychart bar colors for better visibility */ +.markdown article .mermaid .xychart .plot > g > g > rect:nth-child(1) { + fill: #10b981 !important; /* TUnit - Emerald Green */ + opacity: 0.9; +} + +.markdown article .mermaid .xychart .plot > g > g > rect:nth-child(2) { + fill: #ef4444 !important; /* NUnit - Red */ + opacity: 0.85; +} + +.markdown article .mermaid .xychart .plot > g > g > rect:nth-child(3) { + fill: #f59e0b !important; /* MSTest - Amber */ + opacity: 0.85; +} + +.markdown article .mermaid .xychart .plot > g > g > rect:nth-child(4) { + fill: #8b5cf6 !important; /* xUnit - Purple */ + opacity: 0.85; +} + +.markdown article .mermaid .xychart .plot > g > g > rect:nth-child(5) { + fill: #06b6d4 !important; /* TUnit AOT - Cyan */ + opacity: 0.9; +} + +/* Enhance chart readability */ +.markdown article .mermaid .xychart text { + fill: var(--ifm-font-color-base) !important; + font-family: var(--ifm-font-family-base); +} + +.markdown article .mermaid .xychart .title { + font-size: 16px; + font-weight: 600; +} + +/* Grid lines for better readability */ +.markdown article .mermaid .xychart line { + stroke: var(--ifm-color-emphasis-300); + stroke-opacity: 0.5; +} + +/* Axis styling */ +.markdown article .mermaid .xychart .xAxis line, +.markdown article .mermaid .xychart .yAxis line { + stroke: var(--ifm-color-emphasis-500); +} + +/* Dark mode adjustments */ +[data-theme='dark'] .markdown article .mermaid .xychart text { + fill: var(--ifm-font-color-base) !important; +} + +[data-theme='dark'] .markdown article .mermaid .xychart line { + stroke: var(--ifm-color-emphasis-500); +} + +/* Add subtle hover effects */ +.markdown article .mermaid .xychart .plot > g > g > rect { + transition: opacity 0.2s ease; +} + +.markdown article .mermaid .xychart .plot > g > g > rect:hover { + opacity: 1 !important; + filter: brightness(1.1); +} diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index 7d4220ef57..912280f65d 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -4,6 +4,9 @@ * work well for content-centric websites. */ +/* Import benchmark chart styling */ +@import url('./benchmark-charts.css'); + /* You can override the default Infima variables here. */ :root { --ifm-color-primary: #14b8a6; diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx index 0242064ad4..38695fbafe 100644 --- a/docs/src/pages/index.tsx +++ b/docs/src/pages/index.tsx @@ -75,6 +75,12 @@ public async Task MyTest() View Tutorial + + ā¤ļø Sponsor +
diff --git a/docs/static/benchmarks/AsyncTests.json b/docs/static/benchmarks/AsyncTests.json new file mode 100644 index 0000000000..9778baf8a4 --- /dev/null +++ b/docs/static/benchmarks/AsyncTests.json @@ -0,0 +1,51 @@ +{ + "timestamp": "2025-12-10T00:29:52.680Z", + "category": "AsyncTests", + "environment": { + "benchmarkDotNetVersion": "BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.3 LTS (Noble Numbat)", + "sdk": ".NET SDK 10.0.101", + "host": ".NET 10.0.1 (10.0.1, 10.0.125.57005), X64 RyuJIT x86-64-v3" + }, + "results": [ + { + "Method": "TUnit", + "Version": "1.5.6", + "Mean": "567.5 ms", + "Error": "5.69 ms", + "StdDev": "5.32 ms", + "Median": "566.7 ms" + }, + { + "Method": "NUnit", + "Version": "4.4.0", + "Mean": "736.1 ms", + "Error": "7.38 ms", + "StdDev": "6.54 ms", + "Median": "735.9 ms" + }, + { + "Method": "MSTest", + "Version": "4.0.2", + "Mean": "620.1 ms", + "Error": "6.16 ms", + "StdDev": "5.46 ms", + "Median": "620.1 ms" + }, + { + "Method": "xUnit3", + "Version": "3.2.1", + "Mean": "710.4 ms", + "Error": "8.95 ms", + "StdDev": "8.37 ms", + "Median": "713.3 ms" + }, + { + "Method": "TUnit_AOT", + "Version": "1.5.6", + "Mean": "122.6 ms", + "Error": "0.40 ms", + "StdDev": "0.37 ms", + "Median": "122.5 ms" + } + ] +} \ No newline at end of file diff --git a/docs/static/benchmarks/BuildTime.json b/docs/static/benchmarks/BuildTime.json new file mode 100644 index 0000000000..11685d8f06 --- /dev/null +++ b/docs/static/benchmarks/BuildTime.json @@ -0,0 +1,43 @@ +{ + "timestamp": "2025-12-10T00:29:52.682Z", + "category": "BuildTime", + "environment": { + "benchmarkDotNetVersion": "BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.3 LTS (Noble Numbat)", + "sdk": ".NET SDK 10.0.101", + "host": ".NET 10.0.1 (10.0.1, 10.0.125.57005), X64 RyuJIT x86-64-v3" + }, + "results": [ + { + "Method": "Build_TUnit", + "Version": "1.5.6", + "Mean": "2.039 s", + "Error": "0.0194 s", + "StdDev": "0.0181 s", + "Median": "2.039 s" + }, + { + "Method": "Build_NUnit", + "Version": "4.4.0", + "Mean": "1.630 s", + "Error": "0.0159 s", + "StdDev": "0.0141 s", + "Median": "1.630 s" + }, + { + "Method": "Build_MSTest", + "Version": "4.0.2", + "Mean": "1.720 s", + "Error": "0.0115 s", + "StdDev": "0.0102 s", + "Median": "1.719 s" + }, + { + "Method": "Build_xUnit3", + "Version": "3.2.1", + "Mean": "1.627 s", + "Error": "0.0210 s", + "StdDev": "0.0196 s", + "Median": "1.625 s" + } + ] +} \ No newline at end of file diff --git a/docs/static/benchmarks/DataDrivenTests.json b/docs/static/benchmarks/DataDrivenTests.json new file mode 100644 index 0000000000..e979438fb3 --- /dev/null +++ b/docs/static/benchmarks/DataDrivenTests.json @@ -0,0 +1,51 @@ +{ + "timestamp": "2025-12-10T00:29:52.681Z", + "category": "DataDrivenTests", + "environment": { + "benchmarkDotNetVersion": "BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.3 LTS (Noble Numbat)", + "sdk": ".NET SDK 10.0.101", + "host": ".NET 10.0.1 (10.0.1, 10.0.125.57005), X64 RyuJIT x86-64-v3" + }, + "results": [ + { + "Method": "TUnit", + "Version": "1.5.6", + "Mean": "511.20 ms", + "Error": "8.823 ms", + "StdDev": "7.821 ms", + "Median": "508.62 ms" + }, + { + "Method": "NUnit", + "Version": "4.4.0", + "Mean": "654.61 ms", + "Error": "10.639 ms", + "StdDev": "10.449 ms", + "Median": "651.83 ms" + }, + { + "Method": "MSTest", + "Version": "4.0.2", + "Mean": "612.10 ms", + "Error": "8.912 ms", + "StdDev": "7.900 ms", + "Median": "611.26 ms" + }, + { + "Method": "xUnit3", + "Version": "3.2.1", + "Mean": "623.96 ms", + "Error": "12.171 ms", + "StdDev": "18.949 ms", + "Median": "615.51 ms" + }, + { + "Method": "TUnit_AOT", + "Version": "1.5.6", + "Mean": "25.28 ms", + "Error": "0.188 ms", + "StdDev": "0.157 ms", + "Median": "25.31 ms" + } + ] +} \ No newline at end of file diff --git a/docs/static/benchmarks/MassiveParallelTests.json b/docs/static/benchmarks/MassiveParallelTests.json new file mode 100644 index 0000000000..8c25880924 --- /dev/null +++ b/docs/static/benchmarks/MassiveParallelTests.json @@ -0,0 +1,51 @@ +{ + "timestamp": "2025-12-10T00:29:52.681Z", + "category": "MassiveParallelTests", + "environment": { + "benchmarkDotNetVersion": "BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.3 LTS (Noble Numbat)", + "sdk": ".NET SDK 10.0.101", + "host": ".NET 10.0.1 (10.0.1, 10.0.125.57005), X64 RyuJIT x86-64-v3" + }, + "results": [ + { + "Method": "TUnit", + "Version": "1.5.6", + "Mean": "602.8 ms", + "Error": "5.74 ms", + "StdDev": "5.09 ms", + "Median": "602.8 ms" + }, + { + "Method": "NUnit", + "Version": "4.4.0", + "Mean": "1,271.1 ms", + "Error": "13.68 ms", + "StdDev": "12.12 ms", + "Median": "1,269.1 ms" + }, + { + "Method": "MSTest", + "Version": "4.0.2", + "Mean": "2,955.5 ms", + "Error": "7.00 ms", + "StdDev": "6.21 ms", + "Median": "2,953.9 ms" + }, + { + "Method": "xUnit3", + "Version": "3.2.1", + "Mean": "3,055.2 ms", + "Error": "8.74 ms", + "StdDev": "8.18 ms", + "Median": "3,052.6 ms" + }, + { + "Method": "TUnit_AOT", + "Version": "1.5.6", + "Mean": "130.6 ms", + "Error": "0.45 ms", + "StdDev": "0.35 ms", + "Median": "130.7 ms" + } + ] +} \ No newline at end of file diff --git a/docs/static/benchmarks/MatrixTests.json b/docs/static/benchmarks/MatrixTests.json new file mode 100644 index 0000000000..d007288f6a --- /dev/null +++ b/docs/static/benchmarks/MatrixTests.json @@ -0,0 +1,51 @@ +{ + "timestamp": "2025-12-10T00:29:52.681Z", + "category": "MatrixTests", + "environment": { + "benchmarkDotNetVersion": "BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.3 LTS (Noble Numbat)", + "sdk": ".NET SDK 10.0.101", + "host": ".NET 10.0.1 (10.0.1, 10.0.125.57005), X64 RyuJIT x86-64-v3" + }, + "results": [ + { + "Method": "TUnit", + "Version": "1.5.6", + "Mean": "558.23 ms", + "Error": "3.125 ms", + "StdDev": "2.610 ms", + "Median": "558.59 ms" + }, + { + "Method": "NUnit", + "Version": "4.4.0", + "Mean": "1,630.43 ms", + "Error": "8.135 ms", + "StdDev": "6.793 ms", + "Median": "1,629.39 ms" + }, + { + "Method": "MSTest", + "Version": "4.0.2", + "Mean": "1,503.23 ms", + "Error": "11.899 ms", + "StdDev": "11.131 ms", + "Median": "1,500.75 ms" + }, + { + "Method": "xUnit3", + "Version": "3.2.1", + "Mean": "1,590.84 ms", + "Error": "11.724 ms", + "StdDev": "10.966 ms", + "Median": "1,590.40 ms" + }, + { + "Method": "TUnit_AOT", + "Version": "1.5.6", + "Mean": "77.58 ms", + "Error": "0.288 ms", + "StdDev": "0.269 ms", + "Median": "77.58 ms" + } + ] +} \ No newline at end of file diff --git a/docs/static/benchmarks/ScaleTests.json b/docs/static/benchmarks/ScaleTests.json new file mode 100644 index 0000000000..4b984f2f13 --- /dev/null +++ b/docs/static/benchmarks/ScaleTests.json @@ -0,0 +1,51 @@ +{ + "timestamp": "2025-12-10T00:29:52.682Z", + "category": "ScaleTests", + "environment": { + "benchmarkDotNetVersion": "BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.3 LTS (Noble Numbat)", + "sdk": ".NET SDK 10.0.101", + "host": ".NET 10.0.1 (10.0.1, 10.0.125.57005), X64 RyuJIT x86-64-v3" + }, + "results": [ + { + "Method": "TUnit", + "Version": "1.5.6", + "Mean": "518.74 ms", + "Error": "2.874 ms", + "StdDev": "2.688 ms", + "Median": "518.92 ms" + }, + { + "Method": "NUnit", + "Version": "4.4.0", + "Mean": "705.62 ms", + "Error": "9.484 ms", + "StdDev": "8.872 ms", + "Median": "706.08 ms" + }, + { + "Method": "MSTest", + "Version": "4.0.2", + "Mean": "673.90 ms", + "Error": "10.260 ms", + "StdDev": "9.096 ms", + "Median": "672.14 ms" + }, + { + "Method": "xUnit3", + "Version": "3.2.1", + "Mean": "672.99 ms", + "Error": "9.211 ms", + "StdDev": "8.166 ms", + "Median": "672.70 ms" + }, + { + "Method": "TUnit_AOT", + "Version": "1.5.6", + "Mean": "39.44 ms", + "Error": "1.037 ms", + "StdDev": "3.059 ms", + "Median": "39.32 ms" + } + ] +} \ No newline at end of file diff --git a/docs/static/benchmarks/SetupTeardownTests.json b/docs/static/benchmarks/SetupTeardownTests.json new file mode 100644 index 0000000000..7db78afa7c --- /dev/null +++ b/docs/static/benchmarks/SetupTeardownTests.json @@ -0,0 +1,51 @@ +{ + "timestamp": "2025-12-10T00:29:52.682Z", + "category": "SetupTeardownTests", + "environment": { + "benchmarkDotNetVersion": "BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.3 LTS (Noble Numbat)", + "sdk": ".NET SDK 10.0.101", + "host": ".NET 10.0.1 (10.0.1, 10.0.125.57005), X64 RyuJIT x86-64-v3" + }, + "results": [ + { + "Method": "TUnit", + "Version": "1.5.6", + "Mean": "555.3 ms", + "Error": "10.80 ms", + "StdDev": "12.00 ms", + "Median": "554.9 ms" + }, + { + "Method": "NUnit", + "Version": "4.4.0", + "Mean": "1,253.5 ms", + "Error": "7.62 ms", + "StdDev": "7.13 ms", + "Median": "1,252.7 ms" + }, + { + "Method": "MSTest", + "Version": "4.0.2", + "Mean": "1,091.2 ms", + "Error": "10.41 ms", + "StdDev": "9.74 ms", + "Median": "1,091.0 ms" + }, + { + "Method": "xUnit3", + "Version": "3.2.1", + "Mean": "1,177.5 ms", + "Error": "11.46 ms", + "StdDev": "10.72 ms", + "Median": "1,175.1 ms" + }, + { + "Method": "TUnit_AOT", + "Version": "1.5.6", + "Mean": "NA", + "Error": "NA", + "StdDev": "NA", + "Median": "NA" + } + ] +} \ No newline at end of file diff --git a/docs/static/benchmarks/historical.json b/docs/static/benchmarks/historical.json index 9912b13410..74305c85d8 100644 --- a/docs/static/benchmarks/historical.json +++ b/docs/static/benchmarks/historical.json @@ -1,42 +1,126 @@ [ { - "date": "2025-11-07", - "averageSpeedups": { - "vsXUnit": "1.3", - "vsNUnit": "1.2", - "vsMSTest": "1.3", - "count": 1 - }, + "date": "2025-11-11", "environment": "Ubuntu" }, { - "date": "2025-11-08", - "averageSpeedups": { - "vsXUnit": "1.2", - "vsNUnit": "1.1", - "vsMSTest": "1.0", - "count": 1 - }, + "date": "2025-11-11", "environment": "Ubuntu" }, { - "date": "2025-11-09", - "averageSpeedups": { - "vsXUnit": "0.0", - "vsNUnit": "0.0", - "vsMSTest": "0.0", - "count": 1 - }, + "date": "2025-11-12", "environment": "Ubuntu" }, { - "date": "2025-11-10", - "averageSpeedups": { - "vsXUnit": "1.2", - "vsNUnit": "1.2", - "vsMSTest": "1.0", - "count": 1 - }, + "date": "2025-11-12", + "environment": "Ubuntu" + }, + { + "date": "2025-11-12", + "environment": "Ubuntu" + }, + { + "date": "2025-11-12", + "environment": "Ubuntu" + }, + { + "date": "2025-11-12", + "environment": "Ubuntu" + }, + { + "date": "2025-11-12", + "environment": "Ubuntu" + }, + { + "date": "2025-11-13", + "environment": "Ubuntu" + }, + { + "date": "2025-11-14", + "environment": "Ubuntu" + }, + { + "date": "2025-11-15", + "environment": "Ubuntu" + }, + { + "date": "2025-11-16", + "environment": "Ubuntu" + }, + { + "date": "2025-11-17", + "environment": "Ubuntu" + }, + { + "date": "2025-11-18", + "environment": "Ubuntu" + }, + { + "date": "2025-11-19", + "environment": "Ubuntu" + }, + { + "date": "2025-11-20", + "environment": "Ubuntu" + }, + { + "date": "2025-11-23", + "environment": "Ubuntu" + }, + { + "date": "2025-11-24", + "environment": "Ubuntu" + }, + { + "date": "2025-11-25", + "environment": "Ubuntu" + }, + { + "date": "2025-11-26", + "environment": "Ubuntu" + }, + { + "date": "2025-11-27", + "environment": "Ubuntu" + }, + { + "date": "2025-12-01", + "environment": "Ubuntu" + }, + { + "date": "2025-12-02", + "environment": "Ubuntu" + }, + { + "date": "2025-12-03", + "environment": "Ubuntu" + }, + { + "date": "2025-12-04", + "environment": "Ubuntu" + }, + { + "date": "2025-12-05", + "environment": "Ubuntu" + }, + { + "date": "2025-12-06", + "environment": "Ubuntu" + }, + { + "date": "2025-12-07", + "environment": "Ubuntu" + }, + { + "date": "2025-12-08", + "environment": "Ubuntu" + }, + { + "date": "2025-12-09", + "environment": "Ubuntu" + }, + { + "date": "2025-12-10", "environment": "Ubuntu" } ] \ No newline at end of file diff --git a/docs/static/benchmarks/latest.json b/docs/static/benchmarks/latest.json index 145489d3e1..19f6a0365c 100644 --- a/docs/static/benchmarks/latest.json +++ b/docs/static/benchmarks/latest.json @@ -1,51 +1,261 @@ { - "timestamp": "2025-11-10T00:29:09.704Z", + "timestamp": "2025-12-10T00:29:52.683Z", "environment": { - "benchmarkDotNetVersion": "BenchmarkDotNet v0.15.6, Linux Ubuntu 24.04.3 LTS (Noble Numbat)", - "sdk": ".NET SDK 10.0.100-rc.2.25502.107", - "host": ".NET 10.0.0 (10.0.0-rc.2.25502.107, 10.0.25.50307), X64 RyuJIT x86-64-v3" + "benchmarkDotNetVersion": "BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.3 LTS (Noble Numbat)", + "sdk": ".NET SDK 10.0.101", + "host": ".NET 10.0.1 (10.0.1, 10.0.125.57005), X64 RyuJIT x86-64-v3" }, "categories": { - "results": [ + "AsyncTests": [ { "Method": "TUnit", - "Version": "1.0.39", - "Mean": "495.43 ms", - "Error": "2.303 ms", - "StdDev": "2.042 ms", - "Median": "495.43 ms" + "Version": "1.5.6", + "Mean": "567.5 ms", + "Error": "5.69 ms", + "StdDev": "5.32 ms", + "Median": "566.7 ms" }, { "Method": "NUnit", "Version": "4.4.0", - "Mean": "578.77 ms", - "Error": "9.242 ms", - "StdDev": "7.718 ms", - "Median": "580.83 ms" + "Mean": "736.1 ms", + "Error": "7.38 ms", + "StdDev": "6.54 ms", + "Median": "735.9 ms" }, { "Method": "MSTest", - "Version": "4.0.1", - "Mean": "495.62 ms", - "Error": "7.338 ms", - "StdDev": "6.128 ms", - "Median": "496.03 ms" + "Version": "4.0.2", + "Mean": "620.1 ms", + "Error": "6.16 ms", + "StdDev": "5.46 ms", + "Median": "620.1 ms" }, { "Method": "xUnit3", - "Version": "3.2.0", - "Mean": "591.61 ms", - "Error": "10.673 ms", - "StdDev": "9.984 ms", - "Median": "591.28 ms" + "Version": "3.2.1", + "Mean": "710.4 ms", + "Error": "8.95 ms", + "StdDev": "8.37 ms", + "Median": "713.3 ms" }, { "Method": "TUnit_AOT", - "Version": "1.0.39", - "Mean": "44.15 ms", - "Error": "1.112 ms", - "StdDev": "3.244 ms", - "Median": "44.00 ms" + "Version": "1.5.6", + "Mean": "122.6 ms", + "Error": "0.40 ms", + "StdDev": "0.37 ms", + "Median": "122.5 ms" + } + ], + "DataDrivenTests": [ + { + "Method": "TUnit", + "Version": "1.5.6", + "Mean": "511.20 ms", + "Error": "8.823 ms", + "StdDev": "7.821 ms", + "Median": "508.62 ms" + }, + { + "Method": "NUnit", + "Version": "4.4.0", + "Mean": "654.61 ms", + "Error": "10.639 ms", + "StdDev": "10.449 ms", + "Median": "651.83 ms" + }, + { + "Method": "MSTest", + "Version": "4.0.2", + "Mean": "612.10 ms", + "Error": "8.912 ms", + "StdDev": "7.900 ms", + "Median": "611.26 ms" + }, + { + "Method": "xUnit3", + "Version": "3.2.1", + "Mean": "623.96 ms", + "Error": "12.171 ms", + "StdDev": "18.949 ms", + "Median": "615.51 ms" + }, + { + "Method": "TUnit_AOT", + "Version": "1.5.6", + "Mean": "25.28 ms", + "Error": "0.188 ms", + "StdDev": "0.157 ms", + "Median": "25.31 ms" + } + ], + "MassiveParallelTests": [ + { + "Method": "TUnit", + "Version": "1.5.6", + "Mean": "602.8 ms", + "Error": "5.74 ms", + "StdDev": "5.09 ms", + "Median": "602.8 ms" + }, + { + "Method": "NUnit", + "Version": "4.4.0", + "Mean": "1,271.1 ms", + "Error": "13.68 ms", + "StdDev": "12.12 ms", + "Median": "1,269.1 ms" + }, + { + "Method": "MSTest", + "Version": "4.0.2", + "Mean": "2,955.5 ms", + "Error": "7.00 ms", + "StdDev": "6.21 ms", + "Median": "2,953.9 ms" + }, + { + "Method": "xUnit3", + "Version": "3.2.1", + "Mean": "3,055.2 ms", + "Error": "8.74 ms", + "StdDev": "8.18 ms", + "Median": "3,052.6 ms" + }, + { + "Method": "TUnit_AOT", + "Version": "1.5.6", + "Mean": "130.6 ms", + "Error": "0.45 ms", + "StdDev": "0.35 ms", + "Median": "130.7 ms" + } + ], + "MatrixTests": [ + { + "Method": "TUnit", + "Version": "1.5.6", + "Mean": "558.23 ms", + "Error": "3.125 ms", + "StdDev": "2.610 ms", + "Median": "558.59 ms" + }, + { + "Method": "NUnit", + "Version": "4.4.0", + "Mean": "1,630.43 ms", + "Error": "8.135 ms", + "StdDev": "6.793 ms", + "Median": "1,629.39 ms" + }, + { + "Method": "MSTest", + "Version": "4.0.2", + "Mean": "1,503.23 ms", + "Error": "11.899 ms", + "StdDev": "11.131 ms", + "Median": "1,500.75 ms" + }, + { + "Method": "xUnit3", + "Version": "3.2.1", + "Mean": "1,590.84 ms", + "Error": "11.724 ms", + "StdDev": "10.966 ms", + "Median": "1,590.40 ms" + }, + { + "Method": "TUnit_AOT", + "Version": "1.5.6", + "Mean": "77.58 ms", + "Error": "0.288 ms", + "StdDev": "0.269 ms", + "Median": "77.58 ms" + } + ], + "ScaleTests": [ + { + "Method": "TUnit", + "Version": "1.5.6", + "Mean": "518.74 ms", + "Error": "2.874 ms", + "StdDev": "2.688 ms", + "Median": "518.92 ms" + }, + { + "Method": "NUnit", + "Version": "4.4.0", + "Mean": "705.62 ms", + "Error": "9.484 ms", + "StdDev": "8.872 ms", + "Median": "706.08 ms" + }, + { + "Method": "MSTest", + "Version": "4.0.2", + "Mean": "673.90 ms", + "Error": "10.260 ms", + "StdDev": "9.096 ms", + "Median": "672.14 ms" + }, + { + "Method": "xUnit3", + "Version": "3.2.1", + "Mean": "672.99 ms", + "Error": "9.211 ms", + "StdDev": "8.166 ms", + "Median": "672.70 ms" + }, + { + "Method": "TUnit_AOT", + "Version": "1.5.6", + "Mean": "39.44 ms", + "Error": "1.037 ms", + "StdDev": "3.059 ms", + "Median": "39.32 ms" + } + ], + "SetupTeardownTests": [ + { + "Method": "TUnit", + "Version": "1.5.6", + "Mean": "555.3 ms", + "Error": "10.80 ms", + "StdDev": "12.00 ms", + "Median": "554.9 ms" + }, + { + "Method": "NUnit", + "Version": "4.4.0", + "Mean": "1,253.5 ms", + "Error": "7.62 ms", + "StdDev": "7.13 ms", + "Median": "1,252.7 ms" + }, + { + "Method": "MSTest", + "Version": "4.0.2", + "Mean": "1,091.2 ms", + "Error": "10.41 ms", + "StdDev": "9.74 ms", + "Median": "1,091.0 ms" + }, + { + "Method": "xUnit3", + "Version": "3.2.1", + "Mean": "1,177.5 ms", + "Error": "11.46 ms", + "StdDev": "10.72 ms", + "Median": "1,175.1 ms" + }, + { + "Method": "TUnit_AOT", + "Version": "1.5.6", + "Mean": "NA", + "Error": "NA", + "StdDev": "NA", + "Median": "NA" } ] }, @@ -53,58 +263,42 @@ "BuildTime": [ { "Method": "Build_TUnit", - "Version": "1.0.39", - "Mean": "1.788 s", - "Error": "0.0343 s", - "StdDev": "0.0352 s", - "Median": "1.789 s" + "Version": "1.5.6", + "Mean": "2.039 s", + "Error": "0.0194 s", + "StdDev": "0.0181 s", + "Median": "2.039 s" }, { "Method": "Build_NUnit", "Version": "4.4.0", - "Mean": "1.570 s", - "Error": "0.0201 s", - "StdDev": "0.0178 s", - "Median": "1.571 s" + "Mean": "1.630 s", + "Error": "0.0159 s", + "StdDev": "0.0141 s", + "Median": "1.630 s" }, { "Method": "Build_MSTest", - "Version": "4.0.1", - "Mean": "1.657 s", - "Error": "0.0256 s", - "StdDev": "0.0240 s", - "Median": "1.661 s" + "Version": "4.0.2", + "Mean": "1.720 s", + "Error": "0.0115 s", + "StdDev": "0.0102 s", + "Median": "1.719 s" }, { "Method": "Build_xUnit3", - "Version": "3.2.0", - "Mean": "1.577 s", - "Error": "0.0154 s", - "StdDev": "0.0136 s", - "Median": "1.575 s" + "Version": "3.2.1", + "Mean": "1.627 s", + "Error": "0.0210 s", + "StdDev": "0.0196 s", + "Median": "1.625 s" } ] }, - "comparisons": { - "results": { - "tunitMean": 495.43, - "tunitAOTMean": 44.15, - "vsXUnit": "1.19", - "vsNUnit": "1.17", - "vsMSTest": "1.00", - "aotSpeedup": "11.22" - } - }, - "averageSpeedups": { - "vsXUnit": "1.2", - "vsNUnit": "1.2", - "vsMSTest": "1.0", - "count": 1 - }, "stats": { - "runtimeCategories": 1, + "runtimeCategories": 6, "buildCategories": 1, - "totalBenchmarks": 2, - "lastUpdated": "2025-11-10T00:29:09.702Z" + "totalBenchmarks": 7, + "lastUpdated": "2025-12-10T00:29:52.679Z" } } \ No newline at end of file diff --git a/docs/static/benchmarks/results.json b/docs/static/benchmarks/results.json new file mode 100644 index 0000000000..f371056295 --- /dev/null +++ b/docs/static/benchmarks/results.json @@ -0,0 +1,51 @@ +{ + "timestamp": "2025-11-12T20:55:56.395Z", + "category": "results", + "environment": { + "benchmarkDotNetVersion": "BenchmarkDotNet v0.15.6, Linux Ubuntu 24.04.3 LTS (Noble Numbat)", + "sdk": ".NET SDK 10.0.100", + "host": ".NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3" + }, + "results": [ + { + "Method": "TUnit", + "Version": "1.1.0", + "Mean": "564.8 ms", + "Error": "5.71 ms", + "StdDev": "5.34 ms", + "Median": "564.8 ms" + }, + { + "Method": "NUnit", + "Version": "4.4.0", + "Mean": "1,156.4 ms", + "Error": "5.99 ms", + "StdDev": "5.00 ms", + "Median": "1,156.4 ms" + }, + { + "Method": "MSTest", + "Version": "4.0.2", + "Mean": "1,132.6 ms", + "Error": "10.60 ms", + "StdDev": "9.92 ms", + "Median": "1,130.6 ms" + }, + { + "Method": "xUnit3", + "Version": "3.2.0", + "Mean": "1,200.7 ms", + "Error": "6.87 ms", + "StdDev": "5.74 ms", + "Median": "1,200.4 ms" + }, + { + "Method": "TUnit_AOT", + "Version": "1.1.0", + "Mean": "NA", + "Error": "NA", + "StdDev": "NA", + "Median": "NA" + } + ] +} \ No newline at end of file diff --git a/docs/static/benchmarks/summary.json b/docs/static/benchmarks/summary.json new file mode 100644 index 0000000000..a562352763 --- /dev/null +++ b/docs/static/benchmarks/summary.json @@ -0,0 +1,15 @@ +{ + "runtime": [ + "AsyncTests", + "DataDrivenTests", + "MassiveParallelTests", + "MatrixTests", + "ScaleTests", + "SetupTeardownTests" + ], + "build": [ + "BuildTime" + ], + "timestamp": "2025-12-10", + "environment": "Ubuntu Latest • .NET SDK 10.0.101" +} \ No newline at end of file diff --git a/docs/yarn.lock b/docs/yarn.lock index e7b9f20ebd..3ab069b6b7 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -2,22 +2,23 @@ # yarn lockfile v1 -"@ai-sdk/gateway@1.0.29": - version "1.0.29" - resolved "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-1.0.29.tgz" - integrity sha512-o9LtmBiG2WAgs3GAmL79F8idan/UupxHG8Tyr2gP4aUSOzflM0bsvfzozBp8x6WatQnOx+Pio7YNw45Y6I16iw== +"@ai-sdk/gateway@2.0.14": + version "2.0.14" + resolved "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.14.tgz" + integrity sha512-QU+ZVizSXN/V5uWgwapXrCLvkUEmmJeojAbikMH4gLgbeQF3oRugcQm3D8X9B+Rnestbz5cevNap7vKyJT/jfA== dependencies: "@ai-sdk/provider" "2.0.0" - "@ai-sdk/provider-utils" "3.0.9" + "@ai-sdk/provider-utils" "3.0.17" + "@vercel/oidc" "3.0.5" -"@ai-sdk/provider-utils@3.0.9": - version "3.0.9" - resolved "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.9.tgz" - integrity sha512-Pm571x5efqaI4hf9yW4KsVlDBDme8++UepZRnq+kqVBWWjgvGhQlzU8glaFq0YJEB9kkxZHbRRyVeHoV2sRYaQ== +"@ai-sdk/provider-utils@3.0.17": + version "3.0.17" + resolved "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz" + integrity sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw== dependencies: "@ai-sdk/provider" "2.0.0" "@standard-schema/spec" "^1.0.0" - eventsource-parser "^3.0.5" + eventsource-parser "^3.0.6" "@ai-sdk/provider@2.0.0": version "2.0.0" @@ -27,24 +28,24 @@ json-schema "^0.4.0" "@ai-sdk/react@^2.0.30": - version "2.0.52" - resolved "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.52.tgz" - integrity sha512-4/i40pykN4gTGH264+k1g4tMGdw4xN7vZ1qESFCIm/lhS/8YiJPYheBOk9c349hytOT1sGxp3UNPcOWzWS0H2A== + version "2.0.100" + resolved "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.100.tgz" + integrity sha512-bC4cEoX9xfBKKnVyhyrhdRraji71uxpCKlNAwD6u1xPgcsCnz99X+9ByoBKvOHRzBtSFUq2K6016huip8Cvq6w== dependencies: - "@ai-sdk/provider-utils" "3.0.9" - ai "5.0.52" + "@ai-sdk/provider-utils" "3.0.17" + ai "5.0.100" swr "^2.2.5" throttleit "2.1.0" -"@algolia/abtesting@1.4.0": - version "1.4.0" - resolved "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.4.0.tgz" - integrity sha512-N0blWT/C0KOZ/OJ9GXBX66odJZlrYjMj3M+01y8ob1mjBFnBaBo7gOCyHBDQy60+H4pJXp3pSGlJOqJIueBH+A== +"@algolia/abtesting@1.10.0": + version "1.10.0" + resolved "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.10.0.tgz" + integrity sha512-mQT3jwuTgX8QMoqbIR7mPlWkqQqBPQaPabQzm37xg2txMlaMogK/4hCiiESGdg39MlHZOVHeV+0VJuE7f5UK8A== dependencies: - "@algolia/client-common" "5.38.0" - "@algolia/requester-browser-xhr" "5.38.0" - "@algolia/requester-fetch" "5.38.0" - "@algolia/requester-node-http" "5.38.0" + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" "@algolia/autocomplete-core@1.19.2": version "1.19.2" @@ -66,424 +67,382 @@ resolved "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz" integrity sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w== -"@algolia/client-abtesting@5.38.0": - version "5.38.0" - resolved "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.38.0.tgz" - integrity sha512-15d6zv8vtj2l9pnnp/EH7Rhq3/snCCHRz56NnX6xIUPrbJl5gCsIYXAz8C2IEkwOpoDb0r5G6ArY2gKdVMNezw== - dependencies: - "@algolia/client-common" "5.38.0" - "@algolia/requester-browser-xhr" "5.38.0" - "@algolia/requester-fetch" "5.38.0" - "@algolia/requester-node-http" "5.38.0" - -"@algolia/client-analytics@5.38.0": - version "5.38.0" - resolved "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.38.0.tgz" - integrity sha512-jJIbYAhYvTG3+gEAP5Q5Dp6PFJfUR+atz5rsqm5KjAKK+faLFdHJbM2IbOo0xdyGd+SH259MzfQKLJ9mZZ27dQ== - dependencies: - "@algolia/client-common" "5.38.0" - "@algolia/requester-browser-xhr" "5.38.0" - "@algolia/requester-fetch" "5.38.0" - "@algolia/requester-node-http" "5.38.0" - -"@algolia/client-common@5.38.0": - version "5.38.0" - resolved "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.38.0.tgz" - integrity sha512-aMCXzVPGJTeQnVU3Sdf30TfMN2+QyWcjfPTCCHyqVVgjPipb6RnK40aISGoO+rlYjh9LunDsNVFLwv+JEIF8bQ== - -"@algolia/client-common@5.42.0": - version "5.42.0" - resolved "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.42.0.tgz" - integrity sha512-6iiFbm2tRn6B2OqFv9XDTcw5LdWPudiJWIbRk+fsTX+hkPrPm4e1/SbU+lEYBciPoaTShLkDbRge4UePEyCPMQ== - -"@algolia/client-insights@5.38.0": - version "5.38.0" - resolved "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.38.0.tgz" - integrity sha512-4c3FbpMiJX+VcaAj0rYaQdTLS/CkrdOn4hW+5y1plPov7KC7iSHai/VBbirmHuAfW1hVPCIh1w/4erKKTKuo+Q== - dependencies: - "@algolia/client-common" "5.38.0" - "@algolia/requester-browser-xhr" "5.38.0" - "@algolia/requester-fetch" "5.38.0" - "@algolia/requester-node-http" "5.38.0" - -"@algolia/client-personalization@5.38.0": - version "5.38.0" - resolved "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.38.0.tgz" - integrity sha512-FzLs6c8TBL4FSgNfnH2NL7O33ktecGiaKO4ZFG51QYORUzD5d6YwB9UBteaIYu/sgFoEdY57diYU4vyBH8R6iA== - dependencies: - "@algolia/client-common" "5.38.0" - "@algolia/requester-browser-xhr" "5.38.0" - "@algolia/requester-fetch" "5.38.0" - "@algolia/requester-node-http" "5.38.0" - -"@algolia/client-query-suggestions@5.38.0": - version "5.38.0" - resolved "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.38.0.tgz" - integrity sha512-7apiahlgZLvOqrh0+hAYAp/UWjqz6AfSJrCwnsoQNzgIT09dLSPIKREelkuQeUrKy38vHWWpSQE3M0zWSp/YrA== - dependencies: - "@algolia/client-common" "5.38.0" - "@algolia/requester-browser-xhr" "5.38.0" - "@algolia/requester-fetch" "5.38.0" - "@algolia/requester-node-http" "5.38.0" - -"@algolia/client-search@>= 4.9.1 < 6": - version "5.42.0" - resolved "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.42.0.tgz" - integrity sha512-NZR7yyHj2WzK6D5X8gn+/KOxPdzYEXOqVdSaK/biU8QfYUpUuEA0sCWg/XlO05tPVEcJelF/oLrrNY3UjRbOww== - dependencies: - "@algolia/client-common" "5.42.0" - "@algolia/requester-browser-xhr" "5.42.0" - "@algolia/requester-fetch" "5.42.0" - "@algolia/requester-node-http" "5.42.0" - -"@algolia/client-search@5.38.0": - version "5.38.0" - resolved "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.38.0.tgz" - integrity sha512-PTAFMJOpVtJweExEYYgdmSCC6n4V/R+ctDL3fRQy77ulZM/p+zMLIQC9c7HCQE1zqpauvVck3f2zYSejaUTtrw== - dependencies: - "@algolia/client-common" "5.38.0" - "@algolia/requester-browser-xhr" "5.38.0" - "@algolia/requester-fetch" "5.38.0" - "@algolia/requester-node-http" "5.38.0" +"@algolia/client-abtesting@5.44.0": + version "5.44.0" + resolved "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.44.0.tgz" + integrity sha512-KY5CcrWhRTUo/lV7KcyjrZkPOOF9bjgWpMj9z98VA+sXzVpZtkuskBLCKsWYFp2sbwchZFTd3wJM48H0IGgF7g== + dependencies: + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" + +"@algolia/client-analytics@5.44.0": + version "5.44.0" + resolved "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.44.0.tgz" + integrity sha512-LKOCE8S4ewI9bN3ot9RZoYASPi8b78E918/DVPW3HHjCMUe6i+NjbNG6KotU4RpP6AhRWZjjswbOkWelUO+OoA== + dependencies: + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" + +"@algolia/client-common@5.44.0": + version "5.44.0" + resolved "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.44.0.tgz" + integrity sha512-1yyJm4OYC2cztbS28XYVWwLXdwpLsMG4LoZLOltVglQ2+hc/i9q9fUDZyjRa2Bqt4DmkIfezagfMrokhyH4uxQ== + +"@algolia/client-insights@5.44.0": + version "5.44.0" + resolved "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.44.0.tgz" + integrity sha512-wVQWK6jYYsbEOjIMI+e5voLGPUIbXrvDj392IckXaCPvQ6vCMTXakQqOYCd+znQdL76S+3wHDo77HZWiAYKrtA== + dependencies: + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" + +"@algolia/client-personalization@5.44.0": + version "5.44.0" + resolved "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.44.0.tgz" + integrity sha512-lkgRjOjOkqmIkebHjHpU9rLJcJNUDMm+eVSW/KJQYLjGqykEZxal+nYJJTBbLceEU2roByP/+27ZmgIwCdf0iA== + dependencies: + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" + +"@algolia/client-query-suggestions@5.44.0": + version "5.44.0" + resolved "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.44.0.tgz" + integrity sha512-sYfhgwKu6NDVmZHL1WEKVLsOx/jUXCY4BHKLUOcYa8k4COCs6USGgz6IjFkUf+niwq8NCECMmTC4o/fVQOalsA== + dependencies: + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" + +"@algolia/client-search@5.44.0": + version "5.44.0" + resolved "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.44.0.tgz" + integrity sha512-/FRKUM1G4xn3vV8+9xH1WJ9XknU8rkBGlefruq9jDhYUAvYozKimhrmC2pRqw/RyHhPivmgZCRuC8jHP8piz4Q== + dependencies: + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" "@algolia/events@^4.0.1": version "4.0.1" resolved "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz" integrity sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ== -"@algolia/ingestion@1.38.0": - version "1.38.0" - resolved "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.38.0.tgz" - integrity sha512-qGSUGgceJHGyJLZ06bFLwVe2Tpf9KwabmoBjFvFscVmMmU5scKya6voCYd9bdX7V0Xy1qya9MGbmTm4zlLuveQ== - dependencies: - "@algolia/client-common" "5.38.0" - "@algolia/requester-browser-xhr" "5.38.0" - "@algolia/requester-fetch" "5.38.0" - "@algolia/requester-node-http" "5.38.0" - -"@algolia/monitoring@1.38.0": - version "1.38.0" - resolved "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.38.0.tgz" - integrity sha512-VnCtAUcHirvv/dDHg9jK1Z5oo4QOC5FKDxe40x8qloru2qDcjueT34jiAsB0gRos3VWf9v4iPSYTqMIFOcADpQ== +"@algolia/ingestion@1.44.0": + version "1.44.0" + resolved "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.44.0.tgz" + integrity sha512-5+S5ynwMmpTpCLXGjTDpeIa81J+R4BLH0lAojOhmeGSeGEHQTqacl/4sbPyDTcidvnWhaqtyf8m42ue6lvISAw== dependencies: - "@algolia/client-common" "5.38.0" - "@algolia/requester-browser-xhr" "5.38.0" - "@algolia/requester-fetch" "5.38.0" - "@algolia/requester-node-http" "5.38.0" + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" -"@algolia/recommend@5.38.0": - version "5.38.0" - resolved "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.38.0.tgz" - integrity sha512-fqgeU9GqxQorFUeGP4et1MyY28ccf9PCeciHwDPSbPYYiTqBItHdUIiytsNpjC5Dnc0RWtuXWCltLwSw9wN/bQ== +"@algolia/monitoring@1.44.0": + version "1.44.0" + resolved "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.44.0.tgz" + integrity sha512-xhaTN8pXJjR6zkrecg4Cc9YZaQK2LKm2R+LkbAq+AYGBCWJxtSGlNwftozZzkUyq4AXWoyoc0x2SyBtq5LRtqQ== dependencies: - "@algolia/client-common" "5.38.0" - "@algolia/requester-browser-xhr" "5.38.0" - "@algolia/requester-fetch" "5.38.0" - "@algolia/requester-node-http" "5.38.0" + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" -"@algolia/requester-browser-xhr@5.38.0": - version "5.38.0" - resolved "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.38.0.tgz" - integrity sha512-nAUKbv4YQIXbpPi02AQvSPisD5FDDbT8XeYSh9HFoYP0Z3IpBLLDg7R4ahPvzd7gGsVKgEbXzRPWESXSji5yIg== +"@algolia/recommend@5.44.0": + version "5.44.0" + resolved "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.44.0.tgz" + integrity sha512-GNcite/uOIS7wgRU1MT7SdNIupGSW+vbK9igIzMePvD2Dl8dy0O3urKPKIbTuZQqiVH1Cb84y5cgLvwNrdCj/Q== dependencies: - "@algolia/client-common" "5.38.0" + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" -"@algolia/requester-browser-xhr@5.42.0": - version "5.42.0" - resolved "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.42.0.tgz" - integrity sha512-EbuxgteaYBlKgc2Fs3JzoPIKAIaevAIwmv1F+fakaEXeibG4pkmVNsyTUjpOZIgJ1kXeqNvDrcjRb6g3vYBJ9A== +"@algolia/requester-browser-xhr@5.44.0": + version "5.44.0" + resolved "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.44.0.tgz" + integrity sha512-YZHBk72Cd7pcuNHzbhNzF/FbbYszlc7JhZlDyQAchnX5S7tcemSS96F39Sy8t4O4WQLpFvUf1MTNedlitWdOsQ== dependencies: - "@algolia/client-common" "5.42.0" + "@algolia/client-common" "5.44.0" -"@algolia/requester-fetch@5.38.0": - version "5.38.0" - resolved "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.38.0.tgz" - integrity sha512-bkuAHaadC6OxJd3SVyQQnU1oJ9G/zdCqua7fwr1tJDrA/v7KzeS5np4/m6BuRUpTgVgFZHSewGnMcgj9DLBoaQ== +"@algolia/requester-fetch@5.44.0": + version "5.44.0" + resolved "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.44.0.tgz" + integrity sha512-B9WHl+wQ7uf46t9cq+vVM/ypVbOeuldVDq9OtKsX2ApL2g/htx6ImB9ugDOOJmB5+fE31/XPTuCcYz/j03+idA== dependencies: - "@algolia/client-common" "5.38.0" + "@algolia/client-common" "5.44.0" -"@algolia/requester-fetch@5.42.0": - version "5.42.0" - resolved "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.42.0.tgz" - integrity sha512-4vnFvY5Q8QZL9eDNkywFLsk/eQCRBXCBpE8HWs8iUsFNHYoamiOxAeYMin0W/nszQj6abc+jNxMChHmejO+ftQ== +"@algolia/requester-node-http@5.44.0": + version "5.44.0" + resolved "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.44.0.tgz" + integrity sha512-MULm0qeAIk4cdzZ/ehJnl1o7uB5NMokg83/3MKhPq0Pk7+I0uELGNbzIfAkvkKKEYcHALemKdArtySF9eKzh/A== dependencies: - "@algolia/client-common" "5.42.0" + "@algolia/client-common" "5.44.0" -"@algolia/requester-node-http@5.38.0": - version "5.38.0" - resolved "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.38.0.tgz" - integrity sha512-yHDKZTnMPR3/4bY0CVC1/uRnnbAaJ+pctRuX7G/HflBkKOrnUBDEGtQQHzEfMz2FHZ/tbCL+Q9r6mvwTSGp8nw== - dependencies: - "@algolia/client-common" "5.38.0" - -"@algolia/requester-node-http@5.42.0": - version "5.42.0" - resolved "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.42.0.tgz" - integrity sha512-gkLNpU+b1pCIwk1hKTJz2NWQPT8gsfGhQasnZ5QVv4jd79fKRL/1ikd86P0AzuIQs9tbbhlMwxsSTyJmlq502w== +"@antfu/install-pkg@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz" + integrity sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ== dependencies: - "@algolia/client-common" "5.42.0" + package-manager-detector "^1.3.0" + tinyexec "^1.0.1" -"@ampproject/remapping@^2.2.0": - version "2.2.1" - resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz" - integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" +"@antfu/utils@^9.2.0": + version "9.3.0" + resolved "https://registry.npmjs.org/@antfu/utils/-/utils-9.3.0.tgz" + integrity sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA== -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2": - version "7.26.2" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz" - integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== dependencies: - "@babel/helper-validator-identifier" "^7.25.9" + "@babel/helper-validator-identifier" "^7.27.1" js-tokens "^4.0.0" - picocolors "^1.0.0" + picocolors "^1.1.1" -"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.9", "@babel/compat-data@^7.26.0": - version "7.26.2" - resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz" - integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg== - -"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.0.0-0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.12.0", "@babel/core@^7.13.0", "@babel/core@^7.21.3", "@babel/core@^7.25.9", "@babel/core@^7.4.0 || ^8.0.0-0 <8.0.0": - version "7.26.0" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz" - integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.26.0" - "@babel/generator" "^7.26.0" - "@babel/helper-compilation-targets" "^7.25.9" - "@babel/helper-module-transforms" "^7.26.0" - "@babel/helpers" "^7.26.0" - "@babel/parser" "^7.26.0" - "@babel/template" "^7.25.9" - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.26.0" +"@babel/compat-data@^7.27.2", "@babel/compat-data@^7.27.7", "@babel/compat-data@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz" + integrity sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA== + +"@babel/core@^7.21.3", "@babel/core@^7.25.9": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz" + integrity sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.5" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-module-transforms" "^7.28.3" + "@babel/helpers" "^7.28.4" + "@babel/parser" "^7.28.5" + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.5" + "@babel/types" "^7.28.5" + "@jridgewell/remapping" "^2.3.5" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.25.9", "@babel/generator@^7.26.0": - version "7.26.2" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz" - integrity sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw== +"@babel/generator@^7.25.9", "@babel/generator@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz" + integrity sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ== dependencies: - "@babel/parser" "^7.26.2" - "@babel/types" "^7.26.0" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" + "@babel/parser" "^7.28.5" + "@babel/types" "^7.28.5" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" jsesc "^3.0.2" -"@babel/helper-annotate-as-pure@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz" - integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g== +"@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3": + version "7.27.3" + resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz" + integrity sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg== dependencies: - "@babel/types" "^7.25.9" + "@babel/types" "^7.27.3" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz" - integrity sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g== +"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2": + version "7.27.2" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz" - integrity sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ== - dependencies: - "@babel/compat-data" "^7.25.9" - "@babel/helper-validator-option" "^7.25.9" + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" browserslist "^4.24.0" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz" - integrity sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-member-expression-to-functions" "^7.25.9" - "@babel/helper-optimise-call-expression" "^7.25.9" - "@babel/helper-replace-supers" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - "@babel/traverse" "^7.25.9" +"@babel/helper-create-class-features-plugin@^7.27.1", "@babel/helper-create-class-features-plugin@^7.28.3", "@babel/helper-create-class-features-plugin@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz" + integrity sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-member-expression-to-functions" "^7.28.5" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/helper-replace-supers" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/traverse" "^7.28.5" semver "^6.3.1" -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz" - integrity sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw== +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.27.1": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz" + integrity sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw== dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - regexpu-core "^6.1.1" + "@babel/helper-annotate-as-pure" "^7.27.3" + regexpu-core "^6.3.1" semver "^6.3.1" -"@babel/helper-define-polyfill-provider@^0.6.2": - version "0.6.2" - resolved "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz" - integrity sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ== +"@babel/helper-define-polyfill-provider@^0.6.5": + version "0.6.5" + resolved "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz" + integrity sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg== dependencies: - "@babel/helper-compilation-targets" "^7.22.6" - "@babel/helper-plugin-utils" "^7.22.5" - debug "^4.1.1" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-plugin-utils" "^7.27.1" + debug "^4.4.1" lodash.debounce "^4.0.8" - resolve "^1.14.2" - -"@babel/helper-member-expression-to-functions@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz" - integrity sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-module-imports@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz" - integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-module-transforms@^7.25.9", "@babel/helper-module-transforms@^7.26.0": - version "7.26.0" - resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz" - integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== - dependencies: - "@babel/helper-module-imports" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/helper-optimise-call-expression@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz" - integrity sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ== - dependencies: - "@babel/types" "^7.25.9" + resolve "^1.22.10" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-member-expression-to-functions@^7.27.1", "@babel/helper-member-expression-to-functions@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz" + integrity sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg== + dependencies: + "@babel/traverse" "^7.28.5" + "@babel/types" "^7.28.5" + +"@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.3": + version "7.28.3" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz" + integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.28.3" + +"@babel/helper-optimise-call-expression@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz" + integrity sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw== + dependencies: + "@babel/types" "^7.27.1" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + +"@babel/helper-remap-async-to-generator@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz" + integrity sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-wrap-function" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/helper-replace-supers@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz" + integrity sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.27.1" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/helper-skip-transparent-expression-wrappers@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz" + integrity sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helper-wrap-function@^7.27.1": + version "7.28.3" + resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz" + integrity sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g== + dependencies: + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.3" + "@babel/types" "^7.28.2" + +"@babel/helpers@^7.28.4": + version "7.28.4" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz" + integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== + dependencies: + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.8.0": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz" - integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== - -"@babel/helper-remap-async-to-generator@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz" - integrity sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-wrap-function" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/helper-replace-supers@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz" - integrity sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.25.9" - "@babel/helper-optimise-call-expression" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/helper-simple-access@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz" - integrity sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-skip-transparent-expression-wrappers@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz" - integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-string-parser@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz" - integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== - -"@babel/helper-validator-identifier@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz" - integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== - -"@babel/helper-validator-option@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz" - integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== - -"@babel/helper-wrap-function@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz" - integrity sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g== +"@babel/parser@^7.27.2", "@babel/parser@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz" + integrity sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ== dependencies: - "@babel/template" "^7.25.9" - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helpers@^7.26.0": - version "7.26.10" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz" - integrity sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g== - dependencies: - "@babel/template" "^7.26.9" - "@babel/types" "^7.26.10" - -"@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.2", "@babel/parser@^7.26.9": - version "7.26.10" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz" - integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA== - dependencies: - "@babel/types" "^7.26.10" - -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz" - integrity sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g== + "@babel/types" "^7.28.5" + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz" + integrity sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz" - integrity sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz" - integrity sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug== + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/traverse" "^7.28.5" + +"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz" + integrity sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz" + integrity sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz" - integrity sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g== +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz" + integrity sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - "@babel/plugin-transform-optional-chaining" "^7.25.9" - -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz" - integrity sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/traverse" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/plugin-transform-optional-chaining" "^7.27.1" + +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.28.3": + version "7.28.3" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz" + integrity sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/traverse" "^7.28.3" "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": version "7.21.0-placeholder-for-preset-env.2" @@ -497,33 +456,33 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-import-assertions@^7.26.0": - version "7.26.0" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz" - integrity sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg== +"@babel/plugin-syntax-import-assertions@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz" + integrity sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-syntax-import-attributes@^7.26.0": - version "7.26.0" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz" - integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A== +"@babel/plugin-syntax-import-attributes@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz" + integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-syntax-jsx@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz" - integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== +"@babel/plugin-syntax-jsx@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz" + integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-syntax-typescript@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz" - integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ== +"@babel/plugin-syntax-typescript@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz" + integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" @@ -533,531 +492,540 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-arrow-functions@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz" - integrity sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg== +"@babel/plugin-transform-arrow-functions@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz" + integrity sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-async-generator-functions@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz" - integrity sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw== +"@babel/plugin-transform-async-generator-functions@^7.28.0": + version "7.28.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz" + integrity sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-remap-async-to-generator" "^7.25.9" - "@babel/traverse" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-remap-async-to-generator" "^7.27.1" + "@babel/traverse" "^7.28.0" -"@babel/plugin-transform-async-to-generator@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz" - integrity sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ== +"@babel/plugin-transform-async-to-generator@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz" + integrity sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA== dependencies: - "@babel/helper-module-imports" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-remap-async-to-generator" "^7.25.9" + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-remap-async-to-generator" "^7.27.1" -"@babel/plugin-transform-block-scoped-functions@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz" - integrity sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA== +"@babel/plugin-transform-block-scoped-functions@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz" + integrity sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-block-scoping@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz" - integrity sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg== +"@babel/plugin-transform-block-scoping@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz" + integrity sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-class-properties@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz" - integrity sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q== +"@babel/plugin-transform-class-properties@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz" + integrity sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-class-static-block@^7.26.0": - version "7.26.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz" - integrity sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ== +"@babel/plugin-transform-class-static-block@^7.28.3": + version "7.28.3" + resolved "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz" + integrity sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg== dependencies: - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-create-class-features-plugin" "^7.28.3" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-classes@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz" - integrity sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg== +"@babel/plugin-transform-classes@^7.28.4": + version "7.28.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz" + integrity sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA== dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-compilation-targets" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-replace-supers" "^7.25.9" - "@babel/traverse" "^7.25.9" - globals "^11.1.0" + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-globals" "^7.28.0" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-replace-supers" "^7.27.1" + "@babel/traverse" "^7.28.4" -"@babel/plugin-transform-computed-properties@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz" - integrity sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA== +"@babel/plugin-transform-computed-properties@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz" + integrity sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/template" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/template" "^7.27.1" -"@babel/plugin-transform-destructuring@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz" - integrity sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ== +"@babel/plugin-transform-destructuring@^7.28.0", "@babel/plugin-transform-destructuring@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz" + integrity sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/traverse" "^7.28.5" -"@babel/plugin-transform-dotall-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz" - integrity sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA== +"@babel/plugin-transform-dotall-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz" + integrity sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-duplicate-keys@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz" - integrity sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw== +"@babel/plugin-transform-duplicate-keys@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz" + integrity sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz" - integrity sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog== +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz" + integrity sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-dynamic-import@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz" - integrity sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg== +"@babel/plugin-transform-dynamic-import@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz" + integrity sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-exponentiation-operator@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz" - integrity sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA== +"@babel/plugin-transform-explicit-resource-management@^7.28.0": + version "7.28.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz" + integrity sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-transform-destructuring" "^7.28.0" -"@babel/plugin-transform-export-namespace-from@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz" - integrity sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww== +"@babel/plugin-transform-exponentiation-operator@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz" + integrity sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-for-of@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz" - integrity sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A== +"@babel/plugin-transform-export-namespace-from@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz" + integrity sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-function-name@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz" - integrity sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA== +"@babel/plugin-transform-for-of@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz" + integrity sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw== dependencies: - "@babel/helper-compilation-targets" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/traverse" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" -"@babel/plugin-transform-json-strings@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz" - integrity sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw== +"@babel/plugin-transform-function-name@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz" + integrity sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-compilation-targets" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/traverse" "^7.27.1" -"@babel/plugin-transform-literals@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz" - integrity sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ== +"@babel/plugin-transform-json-strings@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz" + integrity sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-logical-assignment-operators@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz" - integrity sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q== +"@babel/plugin-transform-literals@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz" + integrity sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-member-expression-literals@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz" - integrity sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA== +"@babel/plugin-transform-logical-assignment-operators@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz" + integrity sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-modules-amd@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz" - integrity sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw== +"@babel/plugin-transform-member-expression-literals@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz" + integrity sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ== dependencies: - "@babel/helper-module-transforms" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-modules-commonjs@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz" - integrity sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg== +"@babel/plugin-transform-modules-amd@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz" + integrity sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA== dependencies: - "@babel/helper-module-transforms" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-simple-access" "^7.25.9" + "@babel/helper-module-transforms" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-modules-systemjs@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz" - integrity sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA== +"@babel/plugin-transform-modules-commonjs@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz" + integrity sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw== dependencies: - "@babel/helper-module-transforms" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - "@babel/traverse" "^7.25.9" + "@babel/helper-module-transforms" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-modules-systemjs@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz" + integrity sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew== + dependencies: + "@babel/helper-module-transforms" "^7.28.3" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.5" -"@babel/plugin-transform-modules-umd@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz" - integrity sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw== +"@babel/plugin-transform-modules-umd@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz" + integrity sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w== dependencies: - "@babel/helper-module-transforms" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-module-transforms" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-named-capturing-groups-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz" - integrity sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA== +"@babel/plugin-transform-named-capturing-groups-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz" + integrity sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-new-target@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz" - integrity sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ== +"@babel/plugin-transform-new-target@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz" + integrity sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-nullish-coalescing-operator@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz" - integrity sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog== +"@babel/plugin-transform-nullish-coalescing-operator@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz" + integrity sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-numeric-separator@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz" - integrity sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q== +"@babel/plugin-transform-numeric-separator@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz" + integrity sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-object-rest-spread@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz" - integrity sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg== +"@babel/plugin-transform-object-rest-spread@^7.28.4": + version "7.28.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz" + integrity sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew== dependencies: - "@babel/helper-compilation-targets" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/plugin-transform-parameters" "^7.25.9" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-transform-destructuring" "^7.28.0" + "@babel/plugin-transform-parameters" "^7.27.7" + "@babel/traverse" "^7.28.4" -"@babel/plugin-transform-object-super@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz" - integrity sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A== +"@babel/plugin-transform-object-super@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz" + integrity sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-replace-supers" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-replace-supers" "^7.27.1" -"@babel/plugin-transform-optional-catch-binding@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz" - integrity sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g== +"@babel/plugin-transform-optional-catch-binding@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz" + integrity sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-optional-chaining@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz" - integrity sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A== +"@babel/plugin-transform-optional-chaining@^7.27.1", "@babel/plugin-transform-optional-chaining@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz" + integrity sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" -"@babel/plugin-transform-parameters@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz" - integrity sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g== +"@babel/plugin-transform-parameters@^7.27.7": + version "7.27.7" + resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz" + integrity sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-private-methods@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz" - integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw== +"@babel/plugin-transform-private-methods@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz" + integrity sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-private-property-in-object@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz" - integrity sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw== +"@babel/plugin-transform-private-property-in-object@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz" + integrity sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-property-literals@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz" - integrity sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA== +"@babel/plugin-transform-property-literals@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz" + integrity sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-transform-react-constant-elements@^7.21.3": - version "7.24.1" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.24.1.tgz" - integrity sha512-QXp1U9x0R7tkiGB0FOk8o74jhnap0FlZ5gNkRIWdG3eP+SvMFg118e1zaWewDzgABb106QSKpVsD3Wgd8t6ifA== + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.27.1.tgz" + integrity sha512-edoidOjl/ZxvYo4lSBOQGDSyToYVkTAwyVoa2tkuYTSmjrB1+uAedoL5iROVLXkxH+vRgA7uP4tMg2pUJpZ3Ug== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-react-display-name@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz" - integrity sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ== +"@babel/plugin-transform-react-display-name@^7.28.0": + version "7.28.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz" + integrity sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-react-jsx-development@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz" - integrity sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw== +"@babel/plugin-transform-react-jsx-development@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz" + integrity sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q== dependencies: - "@babel/plugin-transform-react-jsx" "^7.25.9" + "@babel/plugin-transform-react-jsx" "^7.27.1" -"@babel/plugin-transform-react-jsx@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz" - integrity sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw== +"@babel/plugin-transform-react-jsx@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz" + integrity sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw== dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-module-imports" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/plugin-syntax-jsx" "^7.25.9" - "@babel/types" "^7.25.9" + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-syntax-jsx" "^7.27.1" + "@babel/types" "^7.27.1" -"@babel/plugin-transform-react-pure-annotations@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz" - integrity sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg== +"@babel/plugin-transform-react-pure-annotations@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz" + integrity sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA== dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-regenerator@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz" - integrity sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg== +"@babel/plugin-transform-regenerator@^7.28.4": + version "7.28.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz" + integrity sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - regenerator-transform "^0.15.2" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-regexp-modifiers@^7.26.0": - version "7.26.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz" - integrity sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw== +"@babel/plugin-transform-regexp-modifiers@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz" + integrity sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-reserved-words@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz" - integrity sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg== +"@babel/plugin-transform-reserved-words@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz" + integrity sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-transform-runtime@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.9.tgz" - integrity sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ== - dependencies: - "@babel/helper-module-imports" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - babel-plugin-polyfill-corejs2 "^0.4.10" - babel-plugin-polyfill-corejs3 "^0.10.6" - babel-plugin-polyfill-regenerator "^0.6.1" + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.5.tgz" + integrity sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + babel-plugin-polyfill-corejs2 "^0.4.14" + babel-plugin-polyfill-corejs3 "^0.13.0" + babel-plugin-polyfill-regenerator "^0.6.5" semver "^6.3.1" -"@babel/plugin-transform-shorthand-properties@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz" - integrity sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng== +"@babel/plugin-transform-shorthand-properties@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz" + integrity sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-spread@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz" - integrity sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A== +"@babel/plugin-transform-spread@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz" + integrity sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" -"@babel/plugin-transform-sticky-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz" - integrity sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA== +"@babel/plugin-transform-sticky-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz" + integrity sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-template-literals@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz" - integrity sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw== +"@babel/plugin-transform-template-literals@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz" + integrity sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-typeof-symbol@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz" - integrity sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA== +"@babel/plugin-transform-typeof-symbol@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz" + integrity sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-typescript@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.9.tgz" - integrity sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ== +"@babel/plugin-transform-typescript@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz" + integrity sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA== dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - "@babel/plugin-syntax-typescript" "^7.25.9" + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-create-class-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/plugin-syntax-typescript" "^7.27.1" -"@babel/plugin-transform-unicode-escapes@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz" - integrity sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q== +"@babel/plugin-transform-unicode-escapes@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz" + integrity sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-unicode-property-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz" - integrity sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg== +"@babel/plugin-transform-unicode-property-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz" + integrity sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-unicode-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz" - integrity sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA== +"@babel/plugin-transform-unicode-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz" + integrity sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-unicode-sets-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz" - integrity sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ== +"@babel/plugin-transform-unicode-sets-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz" + integrity sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" "@babel/preset-env@^7.20.2", "@babel/preset-env@^7.25.9": - version "7.26.0" - resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz" - integrity sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw== - dependencies: - "@babel/compat-data" "^7.26.0" - "@babel/helper-compilation-targets" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-validator-option" "^7.25.9" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.25.9" - "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.25.9" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.25.9" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.25.9" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.25.9" + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz" + integrity sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg== + dependencies: + "@babel/compat-data" "^7.28.5" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-validator-option" "^7.27.1" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.28.5" + "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.27.1" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.27.1" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.27.1" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.28.3" "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-import-assertions" "^7.26.0" - "@babel/plugin-syntax-import-attributes" "^7.26.0" + "@babel/plugin-syntax-import-assertions" "^7.27.1" + "@babel/plugin-syntax-import-attributes" "^7.27.1" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" - "@babel/plugin-transform-arrow-functions" "^7.25.9" - "@babel/plugin-transform-async-generator-functions" "^7.25.9" - "@babel/plugin-transform-async-to-generator" "^7.25.9" - "@babel/plugin-transform-block-scoped-functions" "^7.25.9" - "@babel/plugin-transform-block-scoping" "^7.25.9" - "@babel/plugin-transform-class-properties" "^7.25.9" - "@babel/plugin-transform-class-static-block" "^7.26.0" - "@babel/plugin-transform-classes" "^7.25.9" - "@babel/plugin-transform-computed-properties" "^7.25.9" - "@babel/plugin-transform-destructuring" "^7.25.9" - "@babel/plugin-transform-dotall-regex" "^7.25.9" - "@babel/plugin-transform-duplicate-keys" "^7.25.9" - "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.25.9" - "@babel/plugin-transform-dynamic-import" "^7.25.9" - "@babel/plugin-transform-exponentiation-operator" "^7.25.9" - "@babel/plugin-transform-export-namespace-from" "^7.25.9" - "@babel/plugin-transform-for-of" "^7.25.9" - "@babel/plugin-transform-function-name" "^7.25.9" - "@babel/plugin-transform-json-strings" "^7.25.9" - "@babel/plugin-transform-literals" "^7.25.9" - "@babel/plugin-transform-logical-assignment-operators" "^7.25.9" - "@babel/plugin-transform-member-expression-literals" "^7.25.9" - "@babel/plugin-transform-modules-amd" "^7.25.9" - "@babel/plugin-transform-modules-commonjs" "^7.25.9" - "@babel/plugin-transform-modules-systemjs" "^7.25.9" - "@babel/plugin-transform-modules-umd" "^7.25.9" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.25.9" - "@babel/plugin-transform-new-target" "^7.25.9" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.25.9" - "@babel/plugin-transform-numeric-separator" "^7.25.9" - "@babel/plugin-transform-object-rest-spread" "^7.25.9" - "@babel/plugin-transform-object-super" "^7.25.9" - "@babel/plugin-transform-optional-catch-binding" "^7.25.9" - "@babel/plugin-transform-optional-chaining" "^7.25.9" - "@babel/plugin-transform-parameters" "^7.25.9" - "@babel/plugin-transform-private-methods" "^7.25.9" - "@babel/plugin-transform-private-property-in-object" "^7.25.9" - "@babel/plugin-transform-property-literals" "^7.25.9" - "@babel/plugin-transform-regenerator" "^7.25.9" - "@babel/plugin-transform-regexp-modifiers" "^7.26.0" - "@babel/plugin-transform-reserved-words" "^7.25.9" - "@babel/plugin-transform-shorthand-properties" "^7.25.9" - "@babel/plugin-transform-spread" "^7.25.9" - "@babel/plugin-transform-sticky-regex" "^7.25.9" - "@babel/plugin-transform-template-literals" "^7.25.9" - "@babel/plugin-transform-typeof-symbol" "^7.25.9" - "@babel/plugin-transform-unicode-escapes" "^7.25.9" - "@babel/plugin-transform-unicode-property-regex" "^7.25.9" - "@babel/plugin-transform-unicode-regex" "^7.25.9" - "@babel/plugin-transform-unicode-sets-regex" "^7.25.9" + "@babel/plugin-transform-arrow-functions" "^7.27.1" + "@babel/plugin-transform-async-generator-functions" "^7.28.0" + "@babel/plugin-transform-async-to-generator" "^7.27.1" + "@babel/plugin-transform-block-scoped-functions" "^7.27.1" + "@babel/plugin-transform-block-scoping" "^7.28.5" + "@babel/plugin-transform-class-properties" "^7.27.1" + "@babel/plugin-transform-class-static-block" "^7.28.3" + "@babel/plugin-transform-classes" "^7.28.4" + "@babel/plugin-transform-computed-properties" "^7.27.1" + "@babel/plugin-transform-destructuring" "^7.28.5" + "@babel/plugin-transform-dotall-regex" "^7.27.1" + "@babel/plugin-transform-duplicate-keys" "^7.27.1" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.27.1" + "@babel/plugin-transform-dynamic-import" "^7.27.1" + "@babel/plugin-transform-explicit-resource-management" "^7.28.0" + "@babel/plugin-transform-exponentiation-operator" "^7.28.5" + "@babel/plugin-transform-export-namespace-from" "^7.27.1" + "@babel/plugin-transform-for-of" "^7.27.1" + "@babel/plugin-transform-function-name" "^7.27.1" + "@babel/plugin-transform-json-strings" "^7.27.1" + "@babel/plugin-transform-literals" "^7.27.1" + "@babel/plugin-transform-logical-assignment-operators" "^7.28.5" + "@babel/plugin-transform-member-expression-literals" "^7.27.1" + "@babel/plugin-transform-modules-amd" "^7.27.1" + "@babel/plugin-transform-modules-commonjs" "^7.27.1" + "@babel/plugin-transform-modules-systemjs" "^7.28.5" + "@babel/plugin-transform-modules-umd" "^7.27.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.27.1" + "@babel/plugin-transform-new-target" "^7.27.1" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.27.1" + "@babel/plugin-transform-numeric-separator" "^7.27.1" + "@babel/plugin-transform-object-rest-spread" "^7.28.4" + "@babel/plugin-transform-object-super" "^7.27.1" + "@babel/plugin-transform-optional-catch-binding" "^7.27.1" + "@babel/plugin-transform-optional-chaining" "^7.28.5" + "@babel/plugin-transform-parameters" "^7.27.7" + "@babel/plugin-transform-private-methods" "^7.27.1" + "@babel/plugin-transform-private-property-in-object" "^7.27.1" + "@babel/plugin-transform-property-literals" "^7.27.1" + "@babel/plugin-transform-regenerator" "^7.28.4" + "@babel/plugin-transform-regexp-modifiers" "^7.27.1" + "@babel/plugin-transform-reserved-words" "^7.27.1" + "@babel/plugin-transform-shorthand-properties" "^7.27.1" + "@babel/plugin-transform-spread" "^7.27.1" + "@babel/plugin-transform-sticky-regex" "^7.27.1" + "@babel/plugin-transform-template-literals" "^7.27.1" + "@babel/plugin-transform-typeof-symbol" "^7.27.1" + "@babel/plugin-transform-unicode-escapes" "^7.27.1" + "@babel/plugin-transform-unicode-property-regex" "^7.27.1" + "@babel/plugin-transform-unicode-regex" "^7.27.1" + "@babel/plugin-transform-unicode-sets-regex" "^7.27.1" "@babel/preset-modules" "0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2 "^0.4.10" - babel-plugin-polyfill-corejs3 "^0.10.6" - babel-plugin-polyfill-regenerator "^0.6.1" - core-js-compat "^3.38.1" + babel-plugin-polyfill-corejs2 "^0.4.14" + babel-plugin-polyfill-corejs3 "^0.13.0" + babel-plugin-polyfill-regenerator "^0.6.5" + core-js-compat "^3.43.0" semver "^6.3.1" "@babel/preset-modules@0.1.6-no-external-plugins": @@ -1070,72 +1038,108 @@ esutils "^2.0.2" "@babel/preset-react@^7.18.6", "@babel/preset-react@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.25.9.tgz" - integrity sha512-D3to0uSPiWE7rBrdIICCd0tJSIGpLaaGptna2+w7Pft5xMqLpA1sz99DK5TZ1TjGbdQ/VI1eCSZ06dv3lT4JOw== + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz" + integrity sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-validator-option" "^7.25.9" - "@babel/plugin-transform-react-display-name" "^7.25.9" - "@babel/plugin-transform-react-jsx" "^7.25.9" - "@babel/plugin-transform-react-jsx-development" "^7.25.9" - "@babel/plugin-transform-react-pure-annotations" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-validator-option" "^7.27.1" + "@babel/plugin-transform-react-display-name" "^7.28.0" + "@babel/plugin-transform-react-jsx" "^7.27.1" + "@babel/plugin-transform-react-jsx-development" "^7.27.1" + "@babel/plugin-transform-react-pure-annotations" "^7.27.1" "@babel/preset-typescript@^7.21.0", "@babel/preset-typescript@^7.25.9": - version "7.26.0" - resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz" - integrity sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg== + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz" + integrity sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g== dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-validator-option" "^7.25.9" - "@babel/plugin-syntax-jsx" "^7.25.9" - "@babel/plugin-transform-modules-commonjs" "^7.25.9" - "@babel/plugin-transform-typescript" "^7.25.9" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-validator-option" "^7.27.1" + "@babel/plugin-syntax-jsx" "^7.27.1" + "@babel/plugin-transform-modules-commonjs" "^7.27.1" + "@babel/plugin-transform-typescript" "^7.28.5" "@babel/runtime-corejs3@^7.25.9": - version "7.26.10" - resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.26.10.tgz" - integrity sha512-uITFQYO68pMEYR46AHgQoyBg7KPPJDAbGn4jUTIRgCFJIp88MIBUianVOplhZDEec07bp9zIyr4Kp0FCyQzmWg== + version "7.28.4" + resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.4.tgz" + integrity sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ== dependencies: - core-js-pure "^3.30.2" - regenerator-runtime "^0.14.0" + core-js-pure "^3.43.0" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.25.9", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.25.9": version "7.26.10" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz" integrity sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw== dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.25.9", "@babel/template@^7.26.9": - version "7.26.9" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz" - integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA== +"@babel/template@^7.27.1", "@babel/template@^7.27.2": + version "7.27.2" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/traverse@^7.25.9", "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4", "@babel/traverse@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz" + integrity sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.5" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.5" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.5" + debug "^4.3.1" + +"@babel/types@^7.21.3", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4", "@babel/types@^7.28.5", "@babel/types@^7.4.4": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz" + integrity sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA== dependencies: - "@babel/code-frame" "^7.26.2" - "@babel/parser" "^7.26.9" - "@babel/types" "^7.26.9" + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + +"@braintree/sanitize-url@^7.1.1": + version "7.1.1" + resolved "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz" + integrity sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw== -"@babel/traverse@^7.25.9": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz" - integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== +"@chevrotain/cst-dts-gen@11.0.3": + version "11.0.3" + resolved "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz" + integrity sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ== dependencies: - "@babel/code-frame" "^7.25.9" - "@babel/generator" "^7.25.9" - "@babel/parser" "^7.25.9" - "@babel/template" "^7.25.9" - "@babel/types" "^7.25.9" - debug "^4.3.1" - globals "^11.1.0" + "@chevrotain/gast" "11.0.3" + "@chevrotain/types" "11.0.3" + lodash-es "4.17.21" -"@babel/types@^7.21.3", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.10", "@babel/types@^7.26.9", "@babel/types@^7.4.4": - version "7.26.10" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz" - integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ== +"@chevrotain/gast@11.0.3": + version "11.0.3" + resolved "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz" + integrity sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q== dependencies: - "@babel/helper-string-parser" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" + "@chevrotain/types" "11.0.3" + lodash-es "4.17.21" + +"@chevrotain/regexp-to-ast@11.0.3": + version "11.0.3" + resolved "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz" + integrity sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA== + +"@chevrotain/types@11.0.3": + version "11.0.3" + resolved "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz" + integrity sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ== + +"@chevrotain/utils@11.0.3": + version "11.0.3" + resolved "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz" + integrity sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ== "@colors/colors@1.5.0": version "1.5.0" @@ -1147,22 +1151,22 @@ resolved "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz" integrity sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A== -"@csstools/color-helpers@^5.0.2": - version "5.0.2" - resolved "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz" - integrity sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA== +"@csstools/color-helpers@^5.1.0": + version "5.1.0" + resolved "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz" + integrity sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA== "@csstools/css-calc@^2.1.4": version "2.1.4" resolved "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz" integrity sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ== -"@csstools/css-color-parser@^3.0.10": - version "3.0.10" - resolved "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz" - integrity sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg== +"@csstools/css-color-parser@^3.1.0": + version "3.1.0" + resolved "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz" + integrity sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA== dependencies: - "@csstools/color-helpers" "^5.0.2" + "@csstools/color-helpers" "^5.1.0" "@csstools/css-calc" "^2.1.4" "@csstools/css-parser-algorithms@^3.0.5": @@ -1180,55 +1184,88 @@ resolved "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz" integrity sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ== -"@csstools/postcss-cascade-layers@^5.0.1": - version "5.0.1" - resolved "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.1.tgz" - integrity sha512-XOfhI7GShVcKiKwmPAnWSqd2tBR0uxt+runAxttbSp/LY2U16yAVPmAf7e9q4JJ0d+xMNmpwNDLBXnmRCl3HMQ== +"@csstools/postcss-alpha-function@^1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@csstools/postcss-alpha-function/-/postcss-alpha-function-1.0.1.tgz" + integrity sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-cascade-layers@^5.0.2": + version "5.0.2" + resolved "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.2.tgz" + integrity sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg== dependencies: "@csstools/selector-specificity" "^5.0.0" postcss-selector-parser "^7.0.0" -"@csstools/postcss-color-function@^4.0.10": - version "4.0.10" - resolved "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.10.tgz" - integrity sha512-4dY0NBu7NVIpzxZRgh/Q/0GPSz/jLSw0i/u3LTUor0BkQcz/fNhN10mSWBDsL0p9nDb0Ky1PD6/dcGbhACuFTQ== +"@csstools/postcss-color-function-display-p3-linear@^1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@csstools/postcss-color-function-display-p3-linear/-/postcss-color-function-display-p3-linear-1.0.1.tgz" + integrity sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg== dependencies: - "@csstools/css-color-parser" "^3.0.10" + "@csstools/css-color-parser" "^3.1.0" "@csstools/css-parser-algorithms" "^3.0.5" "@csstools/css-tokenizer" "^3.0.4" - "@csstools/postcss-progressive-custom-properties" "^4.1.0" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" -"@csstools/postcss-color-mix-function@^3.0.10": - version "3.0.10" - resolved "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.10.tgz" - integrity sha512-P0lIbQW9I4ShE7uBgZRib/lMTf9XMjJkFl/d6w4EMNHu2qvQ6zljJGEcBkw/NsBtq/6q3WrmgxSS8kHtPMkK4Q== +"@csstools/postcss-color-function@^4.0.12": + version "4.0.12" + resolved "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.12.tgz" + integrity sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA== dependencies: - "@csstools/css-color-parser" "^3.0.10" + "@csstools/css-color-parser" "^3.1.0" "@csstools/css-parser-algorithms" "^3.0.5" "@csstools/css-tokenizer" "^3.0.4" - "@csstools/postcss-progressive-custom-properties" "^4.1.0" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" -"@csstools/postcss-color-mix-variadic-function-arguments@^1.0.0": - version "1.0.0" - resolved "https://registry.npmjs.org/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.0.tgz" - integrity sha512-Z5WhouTyD74dPFPrVE7KydgNS9VvnjB8qcdes9ARpCOItb4jTnm7cHp4FhxCRUoyhabD0WVv43wbkJ4p8hLAlQ== +"@csstools/postcss-color-mix-function@^3.0.12": + version "3.0.12" + resolved "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.12.tgz" + integrity sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag== dependencies: - "@csstools/css-color-parser" "^3.0.10" + "@csstools/css-color-parser" "^3.1.0" "@csstools/css-parser-algorithms" "^3.0.5" "@csstools/css-tokenizer" "^3.0.4" - "@csstools/postcss-progressive-custom-properties" "^4.1.0" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" -"@csstools/postcss-content-alt-text@^2.0.6": - version "2.0.6" - resolved "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.6.tgz" - integrity sha512-eRjLbOjblXq+byyaedQRSrAejKGNAFued+LcbzT+LCL78fabxHkxYjBbxkroONxHHYu2qxhFK2dBStTLPG3jpQ== +"@csstools/postcss-color-mix-variadic-function-arguments@^1.0.2": + version "1.0.2" + resolved "https://registry.npmjs.org/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.2.tgz" + integrity sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-content-alt-text@^2.0.8": + version "2.0.8" + resolved "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.8.tgz" + integrity sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q== + dependencies: + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-contrast-color-function@^2.0.12": + version "2.0.12" + resolved "https://registry.npmjs.org/@csstools/postcss-contrast-color-function/-/postcss-contrast-color-function-2.0.12.tgz" + integrity sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA== dependencies: + "@csstools/css-color-parser" "^3.1.0" "@csstools/css-parser-algorithms" "^3.0.5" "@csstools/css-tokenizer" "^3.0.4" - "@csstools/postcss-progressive-custom-properties" "^4.1.0" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" "@csstools/postcss-exponential-functions@^2.0.9": @@ -1248,43 +1285,43 @@ "@csstools/utilities" "^2.0.0" postcss-value-parser "^4.2.0" -"@csstools/postcss-gamut-mapping@^2.0.10": - version "2.0.10" - resolved "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.10.tgz" - integrity sha512-QDGqhJlvFnDlaPAfCYPsnwVA6ze+8hhrwevYWlnUeSjkkZfBpcCO42SaUD8jiLlq7niouyLgvup5lh+f1qessg== +"@csstools/postcss-gamut-mapping@^2.0.11": + version "2.0.11" + resolved "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.11.tgz" + integrity sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw== dependencies: - "@csstools/css-color-parser" "^3.0.10" + "@csstools/css-color-parser" "^3.1.0" "@csstools/css-parser-algorithms" "^3.0.5" "@csstools/css-tokenizer" "^3.0.4" -"@csstools/postcss-gradients-interpolation-method@^5.0.10": - version "5.0.10" - resolved "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.10.tgz" - integrity sha512-HHPauB2k7Oits02tKFUeVFEU2ox/H3OQVrP3fSOKDxvloOikSal+3dzlyTZmYsb9FlY9p5EUpBtz0//XBmy+aw== +"@csstools/postcss-gradients-interpolation-method@^5.0.12": + version "5.0.12" + resolved "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.12.tgz" + integrity sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow== dependencies: - "@csstools/css-color-parser" "^3.0.10" + "@csstools/css-color-parser" "^3.1.0" "@csstools/css-parser-algorithms" "^3.0.5" "@csstools/css-tokenizer" "^3.0.4" - "@csstools/postcss-progressive-custom-properties" "^4.1.0" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" -"@csstools/postcss-hwb-function@^4.0.10": - version "4.0.10" - resolved "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.10.tgz" - integrity sha512-nOKKfp14SWcdEQ++S9/4TgRKchooLZL0TUFdun3nI4KPwCjETmhjta1QT4ICQcGVWQTvrsgMM/aLB5We+kMHhQ== +"@csstools/postcss-hwb-function@^4.0.12": + version "4.0.12" + resolved "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.12.tgz" + integrity sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA== dependencies: - "@csstools/css-color-parser" "^3.0.10" + "@csstools/css-color-parser" "^3.1.0" "@csstools/css-parser-algorithms" "^3.0.5" "@csstools/css-tokenizer" "^3.0.4" - "@csstools/postcss-progressive-custom-properties" "^4.1.0" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" -"@csstools/postcss-ic-unit@^4.0.2": - version "4.0.2" - resolved "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.2.tgz" - integrity sha512-lrK2jjyZwh7DbxaNnIUjkeDmU8Y6KyzRBk91ZkI5h8nb1ykEfZrtIVArdIjX4DHMIBGpdHrgP0n4qXDr7OHaKA== +"@csstools/postcss-ic-unit@^4.0.4": + version "4.0.4" + resolved "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.4.tgz" + integrity sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg== dependencies: - "@csstools/postcss-progressive-custom-properties" "^4.1.0" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" postcss-value-parser "^4.2.0" @@ -1293,22 +1330,22 @@ resolved "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz" integrity sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg== -"@csstools/postcss-is-pseudo-class@^5.0.1": - version "5.0.1" - resolved "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.1.tgz" - integrity sha512-JLp3POui4S1auhDR0n8wHd/zTOWmMsmK3nQd3hhL6FhWPaox5W7j1se6zXOG/aP07wV2ww0lxbKYGwbBszOtfQ== +"@csstools/postcss-is-pseudo-class@^5.0.3": + version "5.0.3" + resolved "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.3.tgz" + integrity sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ== dependencies: "@csstools/selector-specificity" "^5.0.0" postcss-selector-parser "^7.0.0" -"@csstools/postcss-light-dark-function@^2.0.9": - version "2.0.9" - resolved "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.9.tgz" - integrity sha512-1tCZH5bla0EAkFAI2r0H33CDnIBeLUaJh1p+hvvsylJ4svsv2wOmJjJn+OXwUZLXef37GYbRIVKX+X+g6m+3CQ== +"@csstools/postcss-light-dark-function@^2.0.11": + version "2.0.11" + resolved "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.11.tgz" + integrity sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA== dependencies: "@csstools/css-parser-algorithms" "^3.0.5" "@csstools/css-tokenizer" "^3.0.4" - "@csstools/postcss-progressive-custom-properties" "^4.1.0" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" "@csstools/postcss-logical-float-and-clear@^3.0.0": @@ -1375,21 +1412,21 @@ dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-oklab-function@^4.0.10": - version "4.0.10" - resolved "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.10.tgz" - integrity sha512-ZzZUTDd0fgNdhv8UUjGCtObPD8LYxMH+MJsW9xlZaWTV8Ppr4PtxlHYNMmF4vVWGl0T6f8tyWAKjoI6vePSgAg== +"@csstools/postcss-oklab-function@^4.0.12": + version "4.0.12" + resolved "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.12.tgz" + integrity sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg== dependencies: - "@csstools/css-color-parser" "^3.0.10" + "@csstools/css-color-parser" "^3.1.0" "@csstools/css-parser-algorithms" "^3.0.5" "@csstools/css-tokenizer" "^3.0.4" - "@csstools/postcss-progressive-custom-properties" "^4.1.0" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" -"@csstools/postcss-progressive-custom-properties@^4.1.0": - version "4.1.0" - resolved "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.1.0.tgz" - integrity sha512-YrkI9dx8U4R8Sz2EJaoeD9fI7s7kmeEBfmO+UURNeL6lQI7VxF6sBE+rSqdCBn4onwqmxFdBU3lTwyYb/lCmxA== +"@csstools/postcss-progressive-custom-properties@^4.2.1": + version "4.2.1" + resolved "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.2.1.tgz" + integrity sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw== dependencies: postcss-value-parser "^4.2.0" @@ -1402,15 +1439,15 @@ "@csstools/css-parser-algorithms" "^3.0.5" "@csstools/css-tokenizer" "^3.0.4" -"@csstools/postcss-relative-color-syntax@^3.0.10": - version "3.0.10" - resolved "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.10.tgz" - integrity sha512-8+0kQbQGg9yYG8hv0dtEpOMLwB9M+P7PhacgIzVzJpixxV4Eq9AUQtQw8adMmAJU1RBBmIlpmtmm3XTRd/T00g== +"@csstools/postcss-relative-color-syntax@^3.0.12": + version "3.0.12" + resolved "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.12.tgz" + integrity sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw== dependencies: - "@csstools/css-color-parser" "^3.0.10" + "@csstools/css-color-parser" "^3.1.0" "@csstools/css-parser-algorithms" "^3.0.5" "@csstools/css-tokenizer" "^3.0.4" - "@csstools/postcss-progressive-custom-properties" "^4.1.0" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" "@csstools/postcss-scope-pseudo-class@^4.0.1": @@ -1438,12 +1475,12 @@ "@csstools/css-parser-algorithms" "^3.0.5" "@csstools/css-tokenizer" "^3.0.4" -"@csstools/postcss-text-decoration-shorthand@^4.0.2": - version "4.0.2" - resolved "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.2.tgz" - integrity sha512-8XvCRrFNseBSAGxeaVTaNijAu+FzUvjwFXtcrynmazGb/9WUdsPCpBX+mHEHShVRq47Gy4peYAoxYs8ltUnmzA== +"@csstools/postcss-text-decoration-shorthand@^4.0.3": + version "4.0.3" + resolved "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.3.tgz" + integrity sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA== dependencies: - "@csstools/color-helpers" "^5.0.2" + "@csstools/color-helpers" "^5.1.0" postcss-value-parser "^4.2.0" "@csstools/postcss-trigonometric-functions@^4.0.9": @@ -1460,10 +1497,10 @@ resolved "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz" integrity sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA== -"@csstools/selector-resolve-nested@^3.0.0": - version "3.0.0" - resolved "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz" - integrity sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ== +"@csstools/selector-resolve-nested@^3.1.0": + version "3.1.0" + resolved "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz" + integrity sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g== "@csstools/selector-specificity@^5.0.0": version "5.0.0" @@ -1480,19 +1517,25 @@ resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@docsearch/css@4.1.0": - version "4.1.0" - resolved "https://registry.npmjs.org/@docsearch/css/-/css-4.1.0.tgz" - integrity sha512-nuNKGjHj/FQeWgE9t+i83QD/V67QiaAmGY7xS9TVCRUiCqSljOgIKlsLoQZKKVwEG8f+OWKdznzZkJxGZ7d06A== +"@docsearch/core@4.3.1": + version "4.3.1" + resolved "https://registry.npmjs.org/@docsearch/core/-/core-4.3.1.tgz" + integrity sha512-ktVbkePE+2h9RwqCUMbWXOoebFyDOxHqImAqfs+lC8yOU+XwEW4jgvHGJK079deTeHtdhUNj0PXHSnhJINvHzQ== + +"@docsearch/css@4.3.2": + version "4.3.2" + resolved "https://registry.npmjs.org/@docsearch/css/-/css-4.3.2.tgz" + integrity sha512-K3Yhay9MgkBjJJ0WEL5MxnACModX9xuNt3UlQQkDEDZJZ0+aeWKtOkxHNndMRkMBnHdYvQjxkm6mdlneOtU1IQ== "@docsearch/react@^3.9.0 || ^4.1.0": - version "4.1.0" - resolved "https://registry.npmjs.org/@docsearch/react/-/react-4.1.0.tgz" - integrity sha512-4GHI7TT3sJZ2Vs4Kjadv7vAkMrTsJqHvzvxO3JA7UT8iPRKaDottG5o5uNshPWhVVaBYPC35Ukf8bfCotGpjSg== + version "4.3.2" + resolved "https://registry.npmjs.org/@docsearch/react/-/react-4.3.2.tgz" + integrity sha512-74SFD6WluwvgsOPqifYOviEEVwDxslxfhakTlra+JviaNcs7KK/rjsPj89kVEoQc9FUxRkAofaJnHIR7pb4TSQ== dependencies: "@ai-sdk/react" "^2.0.30" "@algolia/autocomplete-core" "1.19.2" - "@docsearch/css" "4.1.0" + "@docsearch/core" "4.3.1" + "@docsearch/css" "4.3.2" ai "^5.0.30" algoliasearch "^5.28.0" marked "^16.3.0" @@ -1682,7 +1725,7 @@ utility-types "^3.10.0" webpack "^5.88.1" -"@docusaurus/plugin-content-docs@*", "@docusaurus/plugin-content-docs@3.9.2": +"@docusaurus/plugin-content-docs@3.9.2": version "3.9.2" resolved "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz" integrity sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg== @@ -1873,6 +1916,19 @@ tslib "^2.6.0" utility-types "^3.10.0" +"@docusaurus/theme-mermaid@^3.9.2": + version "3.9.2" + resolved "https://registry.npmjs.org/@docusaurus/theme-mermaid/-/theme-mermaid-3.9.2.tgz" + integrity sha512-5vhShRDq/ntLzdInsQkTdoKWSzw8d1jB17sNPYhA/KvYYFXfuVEGHLM6nrf8MFbV8TruAHDG21Fn3W4lO8GaDw== + dependencies: + "@docusaurus/core" "3.9.2" + "@docusaurus/module-type-aliases" "3.9.2" + "@docusaurus/theme-common" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" + mermaid ">=11.6.0" + tslib "^2.6.0" + "@docusaurus/theme-search-algolia@3.9.2": version "3.9.2" resolved "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.9.2.tgz" @@ -1985,6 +2041,25 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@iconify/types@^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz" + integrity sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg== + +"@iconify/utils@^3.0.1": + version "3.0.2" + resolved "https://registry.npmjs.org/@iconify/utils/-/utils-3.0.2.tgz" + integrity sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ== + dependencies: + "@antfu/install-pkg" "^1.1.0" + "@antfu/utils" "^9.2.0" + "@iconify/types" "^2.0.0" + debug "^4.4.1" + globals "^15.15.0" + kolorist "^1.8.0" + local-pkg "^1.1.1" + mlly "^1.7.4" + "@jest/schemas@^29.6.3": version "29.6.3" resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz" @@ -2004,13 +2079,20 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" -"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.5": - version "0.3.5" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz" - integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" "@jridgewell/resolve-uri@^3.1.0": @@ -2018,11 +2100,6 @@ resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - "@jridgewell/source-map@^0.3.3": version "0.3.5" resolved "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz" @@ -2031,15 +2108,15 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.4.15" - resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== -"@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.25" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== +"@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.31" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" @@ -2049,10 +2126,10 @@ resolved "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz" integrity sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA== -"@jsonjoy.com/buffers@^1.0.0": - version "1.0.0" - resolved "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.0.0.tgz" - integrity sha512-NDigYR3PHqCnQLXYyoLbnEdzMMvzeiCWo1KOut7Q0CoIqg9tUAPKJ1iq/2nFhc5kZtexzutNY0LFjdwWL3Dw3Q== +"@jsonjoy.com/buffers@^1.0.0", "@jsonjoy.com/buffers@^1.2.0": + version "1.2.1" + resolved "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz" + integrity sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA== "@jsonjoy.com/codegen@^1.0.0": version "1.0.0" @@ -2060,19 +2137,20 @@ integrity sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g== "@jsonjoy.com/json-pack@^1.11.0": - version "1.14.0" - resolved "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.14.0.tgz" - integrity sha512-LpWbYgVnKzphN5S6uss4M25jJ/9+m6q6UJoeN6zTkK4xAGhKsiBRPVeF7OYMWonn5repMQbE5vieRXcMUrKDKw== + version "1.21.0" + resolved "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz" + integrity sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg== dependencies: "@jsonjoy.com/base64" "^1.1.2" - "@jsonjoy.com/buffers" "^1.0.0" + "@jsonjoy.com/buffers" "^1.2.0" "@jsonjoy.com/codegen" "^1.0.0" - "@jsonjoy.com/json-pointer" "^1.0.1" + "@jsonjoy.com/json-pointer" "^1.0.2" "@jsonjoy.com/util" "^1.9.0" hyperdyperid "^1.2.0" thingies "^2.5.0" + tree-dump "^1.1.0" -"@jsonjoy.com/json-pointer@^1.0.1": +"@jsonjoy.com/json-pointer@^1.0.2": version "1.0.2" resolved "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz" integrity sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg== @@ -2089,29 +2167,31 @@ "@jsonjoy.com/codegen" "^1.0.0" "@leichtgewicht/ip-codec@^2.0.1": - version "2.0.4" - resolved "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz" - integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== + version "2.0.5" + resolved "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz" + integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw== "@mdx-js/mdx@^3.0.0": - version "3.0.1" - resolved "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.0.1.tgz" - integrity sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA== + version "3.1.1" + resolved "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz" + integrity sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ== dependencies: "@types/estree" "^1.0.0" "@types/estree-jsx" "^1.0.0" "@types/hast" "^3.0.0" "@types/mdx" "^2.0.0" + acorn "^8.0.0" collapse-white-space "^2.0.0" devlop "^1.0.0" - estree-util-build-jsx "^3.0.0" estree-util-is-identifier-name "^3.0.0" - estree-util-to-js "^2.0.0" + estree-util-scope "^1.0.0" estree-walker "^3.0.0" - hast-util-to-estree "^3.0.0" hast-util-to-jsx-runtime "^2.0.0" markdown-extensions "^2.0.0" - periscopic "^3.0.0" + recma-build-jsx "^1.0.0" + recma-jsx "^1.0.0" + recma-stringify "^1.0.0" + rehype-recma "^1.0.0" remark-mdx "^3.0.0" remark-parse "^11.0.0" remark-rehype "^11.0.0" @@ -2123,12 +2203,26 @@ vfile "^6.0.0" "@mdx-js/react@^3.0.0": + version "3.1.0" + resolved "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.0.tgz" + integrity sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ== + dependencies: + "@types/mdx" "^2.0.0" + +"@mdx-js/react@^3.1.1": version "3.1.1" - resolved "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz" + resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-3.1.1.tgz#24bda7fffceb2fe256f954482123cda1be5f5fef" integrity sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw== dependencies: "@types/mdx" "^2.0.0" +"@mermaid-js/parser@^0.6.3": + version "0.6.3" + resolved "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz" + integrity sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA== + dependencies: + langium "3.3.1" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" @@ -2137,7 +2231,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -2281,7 +2375,7 @@ "@svgr/babel-plugin-transform-react-native-svg" "8.1.0" "@svgr/babel-plugin-transform-svg-component" "8.0.0" -"@svgr/core@*", "@svgr/core@8.1.0": +"@svgr/core@8.1.0": version "8.1.0" resolved "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz" integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA== @@ -2345,17 +2439,10 @@ resolved "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== -"@types/acorn@^4.0.0": - version "4.0.6" - resolved "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz" - integrity sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ== - dependencies: - "@types/estree" "*" - "@types/body-parser@*": - version "1.19.5" - resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz" - integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== + version "1.19.6" + resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz" + integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== dependencies: "@types/connect" "*" "@types/node" "*" @@ -2382,6 +2469,216 @@ dependencies: "@types/node" "*" +"@types/d3-array@*": + version "3.2.2" + resolved "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz" + integrity sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw== + +"@types/d3-axis@*": + version "3.0.6" + resolved "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz" + integrity sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-brush@*": + version "3.0.6" + resolved "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz" + integrity sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-chord@*": + version "3.0.6" + resolved "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz" + integrity sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg== + +"@types/d3-color@*": + version "3.1.3" + resolved "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz" + integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== + +"@types/d3-contour@*": + version "3.0.6" + resolved "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz" + integrity sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg== + dependencies: + "@types/d3-array" "*" + "@types/geojson" "*" + +"@types/d3-delaunay@*": + version "6.0.4" + resolved "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz" + integrity sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw== + +"@types/d3-dispatch@*": + version "3.0.7" + resolved "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz" + integrity sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA== + +"@types/d3-drag@*": + version "3.0.7" + resolved "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz" + integrity sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-dsv@*": + version "3.0.7" + resolved "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz" + integrity sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g== + +"@types/d3-ease@*": + version "3.0.2" + resolved "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz" + integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== + +"@types/d3-fetch@*": + version "3.0.7" + resolved "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz" + integrity sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA== + dependencies: + "@types/d3-dsv" "*" + +"@types/d3-force@*": + version "3.0.10" + resolved "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz" + integrity sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw== + +"@types/d3-format@*": + version "3.0.4" + resolved "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz" + integrity sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g== + +"@types/d3-geo@*": + version "3.1.0" + resolved "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz" + integrity sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ== + dependencies: + "@types/geojson" "*" + +"@types/d3-hierarchy@*": + version "3.1.7" + resolved "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz" + integrity sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg== + +"@types/d3-interpolate@*": + version "3.0.4" + resolved "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz" + integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== + dependencies: + "@types/d3-color" "*" + +"@types/d3-path@*": + version "3.1.1" + resolved "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz" + integrity sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg== + +"@types/d3-polygon@*": + version "3.0.2" + resolved "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz" + integrity sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA== + +"@types/d3-quadtree@*": + version "3.0.6" + resolved "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz" + integrity sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg== + +"@types/d3-random@*": + version "3.0.3" + resolved "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz" + integrity sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ== + +"@types/d3-scale-chromatic@*": + version "3.1.0" + resolved "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz" + integrity sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ== + +"@types/d3-scale@*": + version "4.0.9" + resolved "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz" + integrity sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw== + dependencies: + "@types/d3-time" "*" + +"@types/d3-selection@*": + version "3.0.11" + resolved "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz" + integrity sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w== + +"@types/d3-shape@*": + version "3.1.7" + resolved "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz" + integrity sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg== + dependencies: + "@types/d3-path" "*" + +"@types/d3-time-format@*": + version "4.0.3" + resolved "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz" + integrity sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg== + +"@types/d3-time@*": + version "3.0.4" + resolved "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz" + integrity sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g== + +"@types/d3-timer@*": + version "3.0.2" + resolved "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz" + integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== + +"@types/d3-transition@*": + version "3.0.9" + resolved "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz" + integrity sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-zoom@*": + version "3.0.8" + resolved "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz" + integrity sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw== + dependencies: + "@types/d3-interpolate" "*" + "@types/d3-selection" "*" + +"@types/d3@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz" + integrity sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww== + dependencies: + "@types/d3-array" "*" + "@types/d3-axis" "*" + "@types/d3-brush" "*" + "@types/d3-chord" "*" + "@types/d3-color" "*" + "@types/d3-contour" "*" + "@types/d3-delaunay" "*" + "@types/d3-dispatch" "*" + "@types/d3-drag" "*" + "@types/d3-dsv" "*" + "@types/d3-ease" "*" + "@types/d3-fetch" "*" + "@types/d3-force" "*" + "@types/d3-format" "*" + "@types/d3-geo" "*" + "@types/d3-hierarchy" "*" + "@types/d3-interpolate" "*" + "@types/d3-path" "*" + "@types/d3-polygon" "*" + "@types/d3-quadtree" "*" + "@types/d3-random" "*" + "@types/d3-scale" "*" + "@types/d3-scale-chromatic" "*" + "@types/d3-selection" "*" + "@types/d3-shape" "*" + "@types/d3-time" "*" + "@types/d3-time-format" "*" + "@types/d3-timer" "*" + "@types/d3-transition" "*" + "@types/d3-zoom" "*" + "@types/debug@^4.0.0": version "4.1.12" resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz" @@ -2406,9 +2703,9 @@ "@types/json-schema" "*" "@types/estree-jsx@^1.0.0": - version "1.0.4" - resolved "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.4.tgz" - integrity sha512-5idy3hvI9lAMqsyilBM+N+boaCf1MgoefbDxN6KEO5aK17TOHwFAYT9sjxzeKAiIWRUBgLxmZ9mPcnzZXtTcRQ== + version "1.0.5" + resolved "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz" + integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg== dependencies: "@types/estree" "*" @@ -2418,24 +2715,29 @@ integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.21", "@types/express-serve-static-core@^4.17.33": - version "4.19.6" - resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz" - integrity sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A== + version "4.19.7" + resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz" + integrity sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" "@types/send" "*" -"@types/express@*", "@types/express@^4.17.13", "@types/express@^4.17.21": - version "4.17.23" - resolved "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz" - integrity sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ== +"@types/express@*", "@types/express@^4.17.21": + version "4.17.25" + resolved "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz" + integrity sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "^4.17.33" "@types/qs" "*" - "@types/serve-static" "*" + "@types/serve-static" "^1" + +"@types/geojson@*": + version "7946.0.16" + resolved "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz" + integrity sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg== "@types/gtag.js@^0.0.12": version "0.0.12" @@ -2465,14 +2767,14 @@ integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== "@types/http-errors@*": - version "2.0.4" - resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz" - integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + version "2.0.5" + resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz" + integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== "@types/http-proxy@^1.17.8": - version "1.17.14" - resolved "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz" - integrity sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w== + version "1.17.17" + resolved "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz" + integrity sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw== dependencies: "@types/node" "*" @@ -2501,9 +2803,9 @@ integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== "@types/mdast@^4.0.0", "@types/mdast@^4.0.2": - version "4.0.3" - resolved "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz" - integrity sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg== + version "4.0.4" + resolved "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz" + integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== dependencies: "@types/unist" "*" @@ -2518,14 +2820,14 @@ integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== "@types/ms@*": - version "0.7.34" - resolved "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz" - integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== + version "2.1.0" + resolved "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== "@types/node-forge@^1.3.0": - version "1.3.11" - resolved "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz" - integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ== + version "1.3.14" + resolved "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz" + integrity sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw== dependencies: "@types/node" "*" @@ -2547,9 +2849,9 @@ integrity sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ== "@types/qs@*": - version "6.9.11" - resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz" - integrity sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ== + version "6.14.0" + resolved "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz" + integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== "@types/range-parser@*": version "1.2.7" @@ -2582,10 +2884,10 @@ "@types/history" "^4.7.11" "@types/react" "*" -"@types/react@*", "@types/react@>= 16.8.0 < 20.0.0", "@types/react@>=16": - version "19.2.2" - resolved "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz" - integrity sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA== +"@types/react@*": + version "19.1.9" + resolved "https://registry.npmjs.org/@types/react/-/react-19.1.9.tgz" + integrity sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA== dependencies: csstype "^3.0.2" @@ -2602,9 +2904,16 @@ "@types/node" "*" "@types/send@*": - version "0.17.4" - resolved "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz" - integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + version "1.2.1" + resolved "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz" + integrity sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== + dependencies: + "@types/node" "*" + +"@types/send@<1": + version "0.17.6" + resolved "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz" + integrity sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og== dependencies: "@types/mime" "^1" "@types/node" "*" @@ -2616,14 +2925,14 @@ dependencies: "@types/express" "*" -"@types/serve-static@*", "@types/serve-static@^1.15.5": - version "1.15.8" - resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz" - integrity sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg== +"@types/serve-static@^1", "@types/serve-static@^1.15.5": + version "1.15.10" + resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz" + integrity sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw== dependencies: "@types/http-errors" "*" "@types/node" "*" - "@types/send" "*" + "@types/send" "<1" "@types/sockjs@^0.3.36": version "0.3.36" @@ -2632,15 +2941,20 @@ dependencies: "@types/node" "*" +"@types/trusted-types@^2.0.7": + version "2.0.7" + resolved "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz" + integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== + "@types/unist@*", "@types/unist@^3.0.0": - version "3.0.2" - resolved "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz" - integrity sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ== + version "3.0.3" + resolved "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz" + integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== "@types/unist@^2.0.0": - version "2.0.10" - resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz" - integrity sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA== + version "2.0.11" + resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz" + integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== "@types/ws@^8.5.10": version "8.18.1" @@ -2655,18 +2969,23 @@ integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== "@types/yargs@^17.0.8": - version "17.0.32" - resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz" - integrity sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog== + version "17.0.35" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz" + integrity sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg== dependencies: "@types/yargs-parser" "*" "@ungap/structured-clone@^1.0.0": - version "1.2.0" - resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz" - integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + version "1.3.0" + resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +"@vercel/oidc@3.0.5": + version "3.0.5" + resolved "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.5.tgz" + integrity sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw== -"@webassemblyjs/ast@^1.12.1", "@webassemblyjs/ast@1.12.1": +"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": version "1.12.1" resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz" integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== @@ -2767,7 +3086,7 @@ "@webassemblyjs/wasm-gen" "1.12.1" "@webassemblyjs/wasm-parser" "1.12.1" -"@webassemblyjs/wasm-parser@^1.12.1", "@webassemblyjs/wasm-parser@1.12.1": +"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1": version "1.12.1" resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz" integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== @@ -2797,7 +3116,7 @@ resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: +accepts@~1.3.4, accepts@~1.3.8: version "1.3.8" resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== @@ -2815,10 +3134,10 @@ acorn-walk@^8.0.0: resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz" integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== -"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.0.0, acorn@^8.0.4, acorn@^8.14.0, acorn@^8.8.2: - version "8.14.0" - resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz" - integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== +acorn@^8.0.0, acorn@^8.0.4, acorn@^8.14.0, acorn@^8.15.0, acorn@^8.8.2: + version "8.15.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== address@^1.0.1: version "1.2.2" @@ -2833,14 +3152,14 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ai@^5.0.30, ai@5.0.52: - version "5.0.52" - resolved "https://registry.npmjs.org/ai/-/ai-5.0.52.tgz" - integrity sha512-GLlRHjMlvN9+w7UYGxCpUQ8GgCRv5Z+JCprRH3Q8YbXJ/JyIc6EP9+YRUmQsyExX/qQsuehe7y/LLygarbSTOw== +ai@5.0.100, ai@^5.0.30: + version "5.0.100" + resolved "https://registry.npmjs.org/ai/-/ai-5.0.100.tgz" + integrity sha512-+ANP4EJomTcUKdEF3UpVAWEl6DGn+ozDLxVZKXmTV7NRfyEC2cLYcKwoU4o3sKJpqXMUKNzpFlJFBKOcsKdMyg== dependencies: - "@ai-sdk/gateway" "1.0.29" + "@ai-sdk/gateway" "2.0.14" "@ai-sdk/provider" "2.0.0" - "@ai-sdk/provider-utils" "3.0.9" + "@ai-sdk/provider-utils" "3.0.17" "@opentelemetry/api" "1.9.0" ajv-formats@^2.1.1: @@ -2862,7 +3181,7 @@ ajv-keywords@^5.1.0: dependencies: fast-deep-equal "^3.1.3" -ajv@^6.12.5, ajv@^6.9.1: +ajv@^6.12.5: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -2872,42 +3191,42 @@ ajv@^6.12.5, ajv@^6.9.1: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.8.2, ajv@^8.9.0: - version "8.12.0" - resolved "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== +ajv@^8.0.0, ajv@^8.9.0: + version "8.17.1" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== dependencies: - fast-deep-equal "^3.1.1" + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" - uri-js "^4.2.2" algoliasearch-helper@^3.26.0: - version "3.26.0" - resolved "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.26.0.tgz" - integrity sha512-Rv2x3GXleQ3ygwhkhJubhhYGsICmShLAiqtUuJTUkr9uOCOXyF2E71LVT4XDnVffbknv8XgScP4U0Oxtgm+hIw== + version "3.26.1" + resolved "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.26.1.tgz" + integrity sha512-CAlCxm4fYBXtvc5MamDzP6Svu8rW4z9me4DCBY1rQ2UDJ0u0flWmusQ8M3nOExZsLLRcUwUPoRAPMrhzOG3erw== dependencies: "@algolia/events" "^4.0.1" -algoliasearch@^5.28.0, algoliasearch@^5.37.0, "algoliasearch@>= 3.1 < 6", "algoliasearch@>= 4.9.1 < 6": - version "5.38.0" - resolved "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.38.0.tgz" - integrity sha512-8VJKIzheeI9cjuVJhU1hYEVetOTe7LvA+CujAI7yqvYsPtZfVEvv1pg9AeFNtHBg/ZoSLGU5LPijhcY5l3Ea9g== - dependencies: - "@algolia/abtesting" "1.4.0" - "@algolia/client-abtesting" "5.38.0" - "@algolia/client-analytics" "5.38.0" - "@algolia/client-common" "5.38.0" - "@algolia/client-insights" "5.38.0" - "@algolia/client-personalization" "5.38.0" - "@algolia/client-query-suggestions" "5.38.0" - "@algolia/client-search" "5.38.0" - "@algolia/ingestion" "1.38.0" - "@algolia/monitoring" "1.38.0" - "@algolia/recommend" "5.38.0" - "@algolia/requester-browser-xhr" "5.38.0" - "@algolia/requester-fetch" "5.38.0" - "@algolia/requester-node-http" "5.38.0" +algoliasearch@^5.28.0, algoliasearch@^5.37.0: + version "5.44.0" + resolved "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.44.0.tgz" + integrity sha512-f8IpsbdQjzTjr/4mJ/jv5UplrtyMnnciGax6/B0OnLCs2/GJTK13O4Y7Ff1AvJVAaztanH+m5nzPoUq6EAy+aA== + dependencies: + "@algolia/abtesting" "1.10.0" + "@algolia/client-abtesting" "5.44.0" + "@algolia/client-analytics" "5.44.0" + "@algolia/client-common" "5.44.0" + "@algolia/client-insights" "5.44.0" + "@algolia/client-personalization" "5.44.0" + "@algolia/client-query-suggestions" "5.44.0" + "@algolia/client-search" "5.44.0" + "@algolia/ingestion" "1.44.0" + "@algolia/monitoring" "1.44.0" + "@algolia/recommend" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" ansi-align@^3.0.1: version "3.0.1" @@ -2986,18 +3305,18 @@ array-union@^2.1.0: integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== astring@^1.8.0: - version "1.8.6" - resolved "https://registry.npmjs.org/astring/-/astring-1.8.6.tgz" - integrity sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg== + version "1.9.0" + resolved "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz" + integrity sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg== autoprefixer@^10.4.19, autoprefixer@^10.4.21: - version "10.4.21" - resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz" - integrity sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ== + version "10.4.22" + resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz" + integrity sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg== dependencies: - browserslist "^4.24.4" - caniuse-lite "^1.0.30001702" - fraction.js "^4.3.7" + browserslist "^4.27.0" + caniuse-lite "^1.0.30001754" + fraction.js "^5.3.4" normalize-range "^0.1.2" picocolors "^1.1.1" postcss-value-parser "^4.2.0" @@ -3017,29 +3336,29 @@ babel-plugin-dynamic-import-node@^2.3.3: dependencies: object.assign "^4.1.0" -babel-plugin-polyfill-corejs2@^0.4.10: - version "0.4.11" - resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz" - integrity sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q== +babel-plugin-polyfill-corejs2@^0.4.14: + version "0.4.14" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz" + integrity sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg== dependencies: - "@babel/compat-data" "^7.22.6" - "@babel/helper-define-polyfill-provider" "^0.6.2" + "@babel/compat-data" "^7.27.7" + "@babel/helper-define-polyfill-provider" "^0.6.5" semver "^6.3.1" -babel-plugin-polyfill-corejs3@^0.10.6: - version "0.10.6" - resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz" - integrity sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA== +babel-plugin-polyfill-corejs3@^0.13.0: + version "0.13.0" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz" + integrity sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A== dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.2" - core-js-compat "^3.38.0" + "@babel/helper-define-polyfill-provider" "^0.6.5" + core-js-compat "^3.43.0" -babel-plugin-polyfill-regenerator@^0.6.1: - version "0.6.2" - resolved "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz" - integrity sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg== +babel-plugin-polyfill-regenerator@^0.6.5: + version "0.6.5" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz" + integrity sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg== dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.2" + "@babel/helper-define-polyfill-provider" "^0.6.5" bail@^2.0.0: version "2.0.2" @@ -3051,6 +3370,11 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +baseline-browser-mapping@^2.8.25: + version "2.8.30" + resolved "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz" + integrity sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA== + batch@0.6.1: version "0.6.1" resolved "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz" @@ -3062,9 +3386,9 @@ big.js@^5.2.2: integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + version "2.3.0" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== body-parser@1.20.3: version "1.20.3" @@ -3126,13 +3450,20 @@ boxen@^7.0.0: wrap-ansi "^8.1.0" brace-expansion@^1.1.7: - version "1.1.12" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz" - integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + version "1.1.11" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== dependencies: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" @@ -3140,15 +3471,16 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browserslist@^4.0.0, browserslist@^4.23.0, browserslist@^4.24.0, browserslist@^4.24.2, browserslist@^4.24.4, browserslist@^4.25.0, "browserslist@>= 4.21.0": - version "4.25.0" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz" - integrity sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA== +browserslist@^4.0.0, browserslist@^4.23.0, browserslist@^4.24.0, browserslist@^4.26.0, browserslist@^4.27.0, browserslist@^4.28.0: + version "4.28.0" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz" + integrity sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ== dependencies: - caniuse-lite "^1.0.30001718" - electron-to-chromium "^1.5.160" - node-releases "^2.0.19" - update-browserslist-db "^1.1.3" + baseline-browser-mapping "^2.8.25" + caniuse-lite "^1.0.30001754" + electron-to-chromium "^1.5.249" + node-releases "^2.0.27" + update-browserslist-db "^1.1.4" buffer-from@^1.0.0: version "1.1.2" @@ -3190,16 +3522,31 @@ cacheable-request@^10.2.8: normalize-url "^8.0.0" responselike "^3.0.0" -call-bind@^1.0.5, call-bind@^1.0.7: - version "1.0.7" - resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz" - integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== +call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== dependencies: - es-define-property "^1.0.0" es-errors "^1.3.0" function-bind "^1.1.2" + +call-bind@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" get-intrinsic "^1.2.4" - set-function-length "^1.2.1" + set-function-length "^1.2.2" + +call-bound@^1.0.2, call-bound@^1.0.3: + version "1.0.4" + resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" callsites@^3.0.0: version "3.1.0" @@ -3234,10 +3581,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001718: - version "1.0.30001721" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz" - integrity sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001754: + version "1.0.30001756" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz" + integrity sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A== ccount@^2.0.0: version "2.0.1" @@ -3307,6 +3654,25 @@ cheerio@1.0.0-rc.12: parse5 "^7.0.0" parse5-htmlparser2-tree-adapter "^7.0.0" +chevrotain-allstar@~0.3.0: + version "0.3.1" + resolved "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz" + integrity sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw== + dependencies: + lodash-es "^4.17.21" + +chevrotain@~11.0.3: + version "11.0.3" + resolved "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz" + integrity sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw== + dependencies: + "@chevrotain/cst-dts-gen" "11.0.3" + "@chevrotain/gast" "11.0.3" + "@chevrotain/regexp-to-ast" "11.0.3" + "@chevrotain/types" "11.0.3" + "@chevrotain/utils" "11.0.3" + lodash-es "4.17.21" + chokidar@^3.5.3, chokidar@^3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" @@ -3409,6 +3775,11 @@ comma-separated-tokens@^2.0.0: resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz" integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== +commander@7, commander@^7.2.0: + version "7.2.0" + resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + commander@^10.0.0: version "10.0.1" resolved "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz" @@ -3424,11 +3795,6 @@ commander@^5.1.0: resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== -commander@^7.2.0: - version "7.2.0" - resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - commander@^8.3.0: version "8.3.0" resolved "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz" @@ -3439,7 +3805,7 @@ common-path-prefix@^3.0.0: resolved "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz" integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w== -compressible@~2.0.16: +compressible@~2.0.18: version "2.0.18" resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz" integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== @@ -3447,16 +3813,16 @@ compressible@~2.0.16: mime-db ">= 1.43.0 < 2" compression@^1.7.4: - version "1.7.4" - resolved "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + version "1.8.1" + resolved "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz" + integrity sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w== dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" + bytes "3.1.2" + compressible "~2.0.18" debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" + negotiator "~0.6.4" + on-headers "~1.1.0" + safe-buffer "5.2.1" vary "~1.1.2" concat-map@0.0.1: @@ -3464,6 +3830,16 @@ concat-map@0.0.1: resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +confbox@^0.1.8: + version "0.1.8" + resolved "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz" + integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w== + +confbox@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz" + integrity sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ== + config-chain@^1.1.11: version "1.1.13" resolved "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz" @@ -3489,9 +3865,9 @@ connect-history-api-fallback@^2.0.0: integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== consola@^3.2.3: - version "3.2.3" - resolved "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz" - integrity sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ== + version "3.4.2" + resolved "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz" + integrity sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA== content-disposition@0.5.2: version "0.5.2" @@ -3537,17 +3913,17 @@ copy-webpack-plugin@^11.0.0: schema-utils "^4.0.0" serialize-javascript "^6.0.0" -core-js-compat@^3.38.0, core-js-compat@^3.38.1: - version "3.39.0" - resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz" - integrity sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw== +core-js-compat@^3.43.0: + version "3.47.0" + resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.47.0.tgz" + integrity sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ== dependencies: - browserslist "^4.24.2" + browserslist "^4.28.0" -core-js-pure@^3.30.2: - version "3.36.0" - resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.0.tgz" - integrity sha512-cN28qmhRNgbMZZMc/RFu5w8pK9VJzpb2rJVR/lHuZJKwmXnoWOpXmMkxqBB514igkp1Hu8WGROsiOAzUcKdHOQ== +core-js-pure@^3.43.0: + version "3.47.0" + resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.47.0.tgz" + integrity sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw== core-js@^3.31.1: version "3.36.0" @@ -3559,6 +3935,20 @@ core-util-is@~1.0.0: resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cose-base@^1.0.0: + version "1.0.3" + resolved "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz" + integrity sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg== + dependencies: + layout-base "^1.0.0" + +cose-base@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz" + integrity sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g== + dependencies: + layout-base "^2.0.0" + cosmiconfig@^8.1.3, cosmiconfig@^8.3.5: version "8.3.6" resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz" @@ -3593,14 +3983,14 @@ css-blank-pseudo@^7.0.1: postcss-selector-parser "^7.0.0" css-declaration-sorter@^7.2.0: - version "7.2.0" - resolved "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz" - integrity sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow== + version "7.3.0" + resolved "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.0.tgz" + integrity sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ== -css-has-pseudo@^7.0.2: - version "7.0.2" - resolved "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.2.tgz" - integrity sha512-nzol/h+E0bId46Kn2dQH5VElaknX2Sr0hFuB/1EomdC7j+OISt2ZzK7EHX9DZDY53WbIVAR7FYKSO2XnSf07MQ== +css-has-pseudo@^7.0.3: + version "7.0.3" + resolved "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.3.tgz" + integrity sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA== dependencies: "@csstools/selector-specificity" "^5.0.0" postcss-selector-parser "^7.0.0" @@ -3649,9 +4039,9 @@ css-select@^4.1.3: nth-check "^2.0.1" css-select@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz" - integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + version "5.2.2" + resolved "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz" + integrity sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw== dependencies: boolbase "^1.0.0" css-what "^6.1.0" @@ -3680,10 +4070,10 @@ css-what@^6.0.1, css-what@^6.1.0: resolved "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== -cssdb@^8.3.0: - version "8.3.0" - resolved "https://registry.npmjs.org/cssdb/-/cssdb-8.3.0.tgz" - integrity sha512-c7bmItIg38DgGjSwDPZOYF/2o0QU/sSgkWOMyl8votOfgFuyiFKWPesmCGEsrGLxEA9uL540cp8LdaGEjUGsZQ== +cssdb@^8.4.2: + version "8.4.2" + resolved "https://registry.npmjs.org/cssdb/-/cssdb-8.4.2.tgz" + integrity sha512-PzjkRkRUS+IHDJohtxkIczlxPPZqRo0nXplsYXOMBRPjcVRjj1W4DfvRgshUYTVuUigU7ptVYkFJQ7abUB0nyg== cssesc@^3.0.0: version "3.0.0" @@ -3764,18 +4154,314 @@ csstype@^3.0.2: resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== +cytoscape-cose-bilkent@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz" + integrity sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ== + dependencies: + cose-base "^1.0.0" + +cytoscape-fcose@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz" + integrity sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ== + dependencies: + cose-base "^2.2.0" + +cytoscape@^3.29.3: + version "3.33.1" + resolved "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz" + integrity sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ== + +"d3-array@1 - 2": + version "2.12.1" + resolved "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz" + integrity sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ== + dependencies: + internmap "^1.0.0" + +"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: + version "3.2.4" + resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz" + integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== + dependencies: + internmap "1 - 2" + +d3-axis@3: + version "3.0.0" + resolved "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz" + integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== + +d3-brush@3: + version "3.0.0" + resolved "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz" + integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "3" + d3-transition "3" + +d3-chord@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz" + integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== + dependencies: + d3-path "1 - 3" + +"d3-color@1 - 3", d3-color@3: + version "3.1.0" + resolved "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +d3-contour@4: + version "4.0.2" + resolved "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz" + integrity sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA== + dependencies: + d3-array "^3.2.0" + +d3-delaunay@6: + version "6.0.4" + resolved "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz" + integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A== + dependencies: + delaunator "5" + +"d3-dispatch@1 - 3", d3-dispatch@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz" + integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== + +"d3-drag@2 - 3", d3-drag@3: + version "3.0.0" + resolved "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz" + integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== + dependencies: + d3-dispatch "1 - 3" + d3-selection "3" + +"d3-dsv@1 - 3", d3-dsv@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz" + integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== + dependencies: + commander "7" + iconv-lite "0.6" + rw "1" + +"d3-ease@1 - 3", d3-ease@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +d3-fetch@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz" + integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== + dependencies: + d3-dsv "1 - 3" + +d3-force@3: + version "3.0.0" + resolved "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz" + integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== + dependencies: + d3-dispatch "1 - 3" + d3-quadtree "1 - 3" + d3-timer "1 - 3" + +"d3-format@1 - 3", d3-format@3: + version "3.1.0" + resolved "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz" + integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== + +d3-geo@3: + version "3.1.1" + resolved "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz" + integrity sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q== + dependencies: + d3-array "2.5.0 - 3" + +d3-hierarchy@3: + version "3.1.2" + resolved "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz" + integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== + +"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +d3-path@1: + version "1.0.9" + resolved "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz" + integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg== + +"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz" + integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== + +d3-polygon@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz" + integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== + +"d3-quadtree@1 - 3", d3-quadtree@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz" + integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== + +d3-random@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz" + integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== + +d3-sankey@^0.12.3: + version "0.12.3" + resolved "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz" + integrity sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ== + dependencies: + d3-array "1 - 2" + d3-shape "^1.2.0" + +d3-scale-chromatic@3: + version "3.1.0" + resolved "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz" + integrity sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ== + dependencies: + d3-color "1 - 3" + d3-interpolate "1 - 3" + +d3-scale@4: + version "4.0.2" + resolved "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + d3-time-format "2 - 4" + +"d3-selection@2 - 3", d3-selection@3: + version "3.0.0" + resolved "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz" + integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== + +d3-shape@3: + version "3.2.0" + resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz" + integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== + dependencies: + d3-path "^3.1.0" + +d3-shape@^1.2.0: + version "1.3.7" + resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz" + integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw== + dependencies: + d3-path "1" + +"d3-time-format@2 - 4", d3-time-format@4: + version "4.1.0" + resolved "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: + version "3.1.0" + resolved "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz" + integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== + dependencies: + d3-array "2 - 3" + +"d3-timer@1 - 3", d3-timer@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + +"d3-transition@2 - 3", d3-transition@3: + version "3.0.1" + resolved "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz" + integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== + dependencies: + d3-color "1 - 3" + d3-dispatch "1 - 3" + d3-ease "1 - 3" + d3-interpolate "1 - 3" + d3-timer "1 - 3" + +d3-zoom@3: + version "3.0.0" + resolved "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz" + integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "2 - 3" + d3-transition "2 - 3" + +d3@^7.9.0: + version "7.9.0" + resolved "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz" + integrity sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA== + dependencies: + d3-array "3" + d3-axis "3" + d3-brush "3" + d3-chord "3" + d3-color "3" + d3-contour "4" + d3-delaunay "6" + d3-dispatch "3" + d3-drag "3" + d3-dsv "3" + d3-ease "3" + d3-fetch "3" + d3-force "3" + d3-format "3" + d3-geo "3" + d3-hierarchy "3" + d3-interpolate "3" + d3-path "3" + d3-polygon "3" + d3-quadtree "3" + d3-random "3" + d3-scale "4" + d3-scale-chromatic "3" + d3-selection "3" + d3-shape "3" + d3-time "3" + d3-time-format "4" + d3-timer "3" + d3-transition "3" + d3-zoom "3" + +dagre-d3-es@7.0.13: + version "7.0.13" + resolved "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz" + integrity sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q== + dependencies: + d3 "^7.9.0" + lodash-es "^4.17.21" + +dayjs@^1.11.18: + version "1.11.19" + resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz" + integrity sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw== + debounce@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz" integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== -debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@4: - version "4.3.4" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - debug@2.6.9: version "2.6.9" resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" @@ -3783,10 +4469,17 @@ debug@2.6.9: dependencies: ms "2.0.0" +debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.3.1, debug@^4.4.1: + version "4.4.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + decode-named-character-reference@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz" - integrity sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg== + version "1.2.0" + resolved "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz" + integrity sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q== dependencies: character-entities "^2.0.0" @@ -3808,14 +4501,14 @@ deepmerge@^4.3.1: integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== default-browser-id@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz" - integrity sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA== + version "5.0.1" + resolved "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz" + integrity sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q== default-browser@^5.2.1: - version "5.2.1" - resolved "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz" - integrity sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg== + version "5.4.0" + resolved "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz" + integrity sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg== dependencies: bundle-name "^4.1.0" default-browser-id "^5.0.0" @@ -3825,7 +4518,7 @@ defer-to-connect@^2.0.1: resolved "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz" integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== -define-data-property@^1.0.1, define-data-property@^1.1.2: +define-data-property@^1.0.1, define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz" integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== @@ -3853,16 +4546,23 @@ define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" - integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== +delaunator@5: + version "5.0.1" + resolved "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz" + integrity sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw== + dependencies: + robust-predicates "^3.0.2" depd@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + dequal@^2.0.0, dequal@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz" @@ -3907,6 +4607,15 @@ dns-packet@^5.2.2: dependencies: "@leichtgewicht/ip-codec" "^2.0.1" +docusaurus-plugin-llms@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/docusaurus-plugin-llms/-/docusaurus-plugin-llms-0.2.2.tgz" + integrity sha512-DZlZ6cv9p5poFE00Qg78aurBNWhLa4o0VhH4kI33DUT0y4ydlFEJJbf8Bks9BuuGPFbY/Guebn+hRc2QymMImg== + dependencies: + gray-matter "^4.0.3" + minimatch "^9.0.3" + yaml "^2.8.1" + dom-converter@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz" @@ -3951,6 +4660,13 @@ domhandler@^5.0.2, domhandler@^5.0.3: dependencies: domelementtype "^2.3.0" +dompurify@^3.2.5: + version "3.3.0" + resolved "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz" + integrity sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ== + optionalDependencies: + "@types/trusted-types" "^2.0.7" + domutils@^2.5.2, domutils@^2.8.0: version "2.8.0" resolved "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz" @@ -3961,9 +4677,9 @@ domutils@^2.5.2, domutils@^2.8.0: domhandler "^4.2.0" domutils@^3.0.1: - version "3.1.0" - resolved "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz" - integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + version "3.2.2" + resolved "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz" + integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw== dependencies: dom-serializer "^2.0.0" domelementtype "^2.3.0" @@ -3984,6 +4700,15 @@ dot-prop@^6.0.1: dependencies: is-obj "^2.0.0" +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + duplexer@^0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz" @@ -3999,10 +4724,10 @@ ee-first@1.1.1: resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.5.160: - version "1.5.165" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz" - integrity sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw== +electron-to-chromium@^1.5.249: + version "1.5.259" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz" + integrity sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ== emoji-regex@^8.0.0: version "8.0.0" @@ -4025,9 +4750,9 @@ emojis-list@^3.0.0: integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== emoticon@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/emoticon/-/emoticon-4.0.1.tgz" - integrity sha512-dqx7eA9YaqyvYtUhJwT4rC1HIp82j5ybS1/vQ42ur+jBe17dJMwZE4+gvL1XadSFfxaPFFGt3Xsw+Y8akThDlw== + version "4.1.0" + resolved "https://registry.npmjs.org/emoticon/-/emoticon-4.1.0.tgz" + integrity sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ== encodeurl@~1.0.2: version "1.0.2" @@ -4057,19 +4782,22 @@ entities@^4.2.0, entities@^4.4.0: resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== +entities@^6.0.0: + version "6.0.1" + resolved "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz" + integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== + error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + version "1.3.4" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz" + integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== dependencies: is-arrayish "^0.2.1" -es-define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz" - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== - dependencies: - get-intrinsic "^1.2.4" +es-define-property@^1.0.0, es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== es-errors@^1.3.0: version "1.3.0" @@ -4081,6 +4809,33 @@ es-module-lexer@^1.2.1: resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz" integrity sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w== +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +esast-util-from-estree@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz" + integrity sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ== + dependencies: + "@types/estree-jsx" "^1.0.0" + devlop "^1.0.0" + estree-util-visit "^2.0.0" + unist-util-position-from-estree "^2.0.0" + +esast-util-from-js@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz" + integrity sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw== + dependencies: + "@types/estree-jsx" "^1.0.0" + acorn "^8.0.0" + esast-util-from-estree "^2.0.0" + vfile-message "^4.0.0" + escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz" @@ -4163,6 +4918,14 @@ estree-util-is-identifier-name@^3.0.0: resolved "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz" integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== +estree-util-scope@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz" + integrity sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ== + dependencies: + "@types/estree" "^1.0.0" + devlop "^1.0.0" + estree-util-to-js@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz" @@ -4173,9 +4936,9 @@ estree-util-to-js@^2.0.0: source-map "^0.7.0" estree-util-value-to-estree@^3.0.1: - version "3.3.3" - resolved "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.3.3.tgz" - integrity sha512-Db+m1WSD4+mUO7UgMeKkAwdbfNWwIxLt48XF2oFU9emPfXkIu+k5/nlOj313v7wqtAPo0f9REhUvznFrPkG8CQ== + version "3.5.0" + resolved "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.5.0.tgz" + integrity sha512-aMV56R27Gv3QmfmF1MY12GWkGzzeAezAX+UplqHVASfjc9wNzI/X6hC0S9oxq61WT4aQesLGslWP9tKk6ghRZQ== dependencies: "@types/estree" "^1.0.0" @@ -4227,7 +4990,7 @@ events@^3.2.0: resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -eventsource-parser@^3.0.5: +eventsource-parser@^3.0.6: version "3.0.6" resolved "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz" integrity sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg== @@ -4284,6 +5047,11 @@ express@^4.21.2: utils-merge "1.0.1" vary "~1.1.2" +exsolve@^1.0.7: + version "1.0.8" + resolved "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz" + integrity sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA== + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz" @@ -4302,25 +5070,30 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-glob@^3.2.11, fast-glob@^3.2.9, fast-glob@^3.3.0: - version "3.3.2" - resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz" - integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + version "3.3.3" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" glob-parent "^5.1.2" merge2 "^1.3.0" - micromatch "^4.0.4" + micromatch "^4.0.8" fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-uri@^3.0.1: + version "3.1.0" + resolved "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== + fastq@^1.6.0: - version "1.17.1" - resolved "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz" - integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + version "1.19.1" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== dependencies: reusify "^1.0.4" @@ -4352,7 +5125,7 @@ figures@^3.2.0: dependencies: escape-string-regexp "^1.0.5" -file-loader@*, file-loader@^6.2.0: +file-loader@^6.2.0: version "6.2.0" resolved "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz" integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== @@ -4402,9 +5175,9 @@ flat@^5.0.2: integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== follow-redirects@^1.0.0: - version "1.15.6" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz" - integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== + version "1.15.11" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz" + integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== form-data-encoder@^2.1.2: version "2.1.4" @@ -4421,10 +5194,10 @@ forwarded@0.2.0: resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== -fraction.js@^4.3.7: - version "4.3.7" - resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz" - integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== +fraction.js@^5.3.4: + version "5.3.4" + resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz" + integrity sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ== fresh@0.5.2: version "0.5.2" @@ -4440,6 +5213,11 @@ fs-extra@^11.1.1, fs-extra@^11.2.0: jsonfile "^6.0.1" universalify "^2.0.0" +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" @@ -4450,22 +5228,35 @@ gensync@^1.0.0-beta.2: resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-intrinsic@^1.1.3, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== +get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" es-errors "^1.3.0" + es-object-atoms "^1.1.1" function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz" integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" @@ -4491,9 +5282,9 @@ glob-parent@^6.0.1: is-glob "^4.0.3" glob-to-regex.js@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.0.1.tgz" - integrity sha512-CG/iEvgQqfzoVsMUbxSJcwbG2JwyZ3naEqPkeltwl0BSS8Bp83k3xlGms+0QdWFUAwV+uvo80wNswKF6FWEkKg== + version "1.2.0" + resolved "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz" + integrity sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ== glob-to-regexp@^0.4.1: version "0.4.1" @@ -4507,10 +5298,10 @@ global-dirs@^3.0.0: dependencies: ini "2.0.0" -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +globals@^15.15.0: + version "15.15.0" + resolved "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz" + integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg== globby@^11.1.0: version "11.1.0" @@ -4535,12 +5326,10 @@ globby@^13.1.1: merge2 "^1.4.1" slash "^4.0.0" -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" +gopd@^1.0.1, gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== got@^12.1.0: version "12.6.1" @@ -4559,16 +5348,16 @@ got@^12.1.0: p-cancelable "^3.0.0" responselike "^3.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: - version "4.2.11" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - graceful-fs@4.2.10: version "4.2.10" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + gray-matter@^4.0.3: version "4.0.3" resolved "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz" @@ -4586,6 +5375,11 @@ gzip-size@^6.0.0: dependencies: duplexer "^0.1.2" +hachure-fill@^0.5.2: + version "0.5.2" + resolved "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz" + integrity sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg== + handle-thing@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz" @@ -4596,45 +5390,40 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.1: +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz" integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== dependencies: es-define-property "^1.0.0" -has-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz" - integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== - -has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== has-yarn@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz" integrity sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA== -hasown@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz" - integrity sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA== +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: function-bind "^1.1.2" hast-util-from-parse5@^8.0.0: - version "8.0.1" - resolved "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz" - integrity sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ== + version "8.0.3" + resolved "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz" + integrity sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg== dependencies: "@types/hast" "^3.0.0" "@types/unist" "^3.0.0" devlop "^1.0.0" - hastscript "^8.0.0" - property-information "^6.0.0" + hastscript "^9.0.0" + property-information "^7.0.0" vfile "^6.0.0" vfile-location "^5.0.0" web-namespaces "^2.0.0" @@ -4647,9 +5436,9 @@ hast-util-parse-selector@^4.0.0: "@types/hast" "^3.0.0" hast-util-raw@^9.0.0: - version "9.0.2" - resolved "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.2.tgz" - integrity sha512-PldBy71wO9Uq1kyaMch9AHIghtQvIwxBUkv823pKmkTM3oV1JxtsTNYdevMxvUHqcnOAuO65JKU2+0NOxc2ksA== + version "9.1.0" + resolved "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz" + integrity sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw== dependencies: "@types/hast" "^3.0.0" "@types/unist" "^3.0.0" @@ -4666,9 +5455,9 @@ hast-util-raw@^9.0.0: zwitch "^2.0.0" hast-util-to-estree@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.0.tgz" - integrity sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw== + version "3.1.3" + resolved "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz" + integrity sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w== dependencies: "@types/estree" "^1.0.0" "@types/estree-jsx" "^1.0.0" @@ -4681,16 +5470,16 @@ hast-util-to-estree@^3.0.0: mdast-util-mdx-expression "^2.0.0" mdast-util-mdx-jsx "^3.0.0" mdast-util-mdxjs-esm "^2.0.0" - property-information "^6.0.0" + property-information "^7.0.0" space-separated-tokens "^2.0.0" - style-to-object "^0.4.0" + style-to-js "^1.0.0" unist-util-position "^5.0.0" zwitch "^2.0.0" hast-util-to-jsx-runtime@^2.0.0: - version "2.3.0" - resolved "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz" - integrity sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ== + version "2.3.6" + resolved "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz" + integrity sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg== dependencies: "@types/estree" "^1.0.0" "@types/hast" "^3.0.0" @@ -4702,9 +5491,9 @@ hast-util-to-jsx-runtime@^2.0.0: mdast-util-mdx-expression "^2.0.0" mdast-util-mdx-jsx "^3.0.0" mdast-util-mdxjs-esm "^2.0.0" - property-information "^6.0.0" + property-information "^7.0.0" space-separated-tokens "^2.0.0" - style-to-object "^1.0.0" + style-to-js "^1.0.0" unist-util-position "^5.0.0" vfile-message "^4.0.0" @@ -4728,15 +5517,15 @@ hast-util-whitespace@^3.0.0: dependencies: "@types/hast" "^3.0.0" -hastscript@^8.0.0: - version "8.0.0" - resolved "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz" - integrity sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw== +hastscript@^9.0.0: + version "9.0.1" + resolved "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz" + integrity sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w== dependencies: "@types/hast" "^3.0.0" comma-separated-tokens "^2.0.0" hast-util-parse-selector "^4.0.0" - property-information "^6.0.0" + property-information "^7.0.0" space-separated-tokens "^2.0.0" he@^1.2.0: @@ -4855,16 +5644,6 @@ http-deceiver@^1.2.7: resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz" integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - http-errors@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" @@ -4876,10 +5655,20 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz" + integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + http-parser-js@>=0.5.1: - version "0.5.8" - resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz" - integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== + version "0.5.10" + resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz" + integrity sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA== http-proxy-middleware@^2.0.9: version "2.0.9" @@ -4926,15 +5715,22 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@0.6: + version "0.6.3" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + icss-utils@^5.0.0, icss-utils@^5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== ignore@^5.2.0, ignore@^5.2.4: - version "5.3.1" - resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz" - integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + version "5.3.2" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== image-size@^2.0.2: version "2.0.2" @@ -4942,9 +5738,9 @@ image-size@^2.0.2: integrity sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w== import-fresh@^3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + version "3.3.1" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== dependencies: parent-module "^1.0.0" resolve-from "^4.0.0" @@ -4969,40 +5765,40 @@ infima@0.2.0-alpha.45: resolved "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.45.tgz" integrity sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw== -inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3, inherits@2.0.4: - version "2.0.4" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - inherits@2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== -ini@^1.3.4: - version "1.3.8" - resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -ini@~1.3.0: - version "1.3.8" - resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== ini@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== -inline-style-parser@0.1.1: - version "0.1.1" - resolved "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz" - integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== +ini@^1.3.4, ini@~1.3.0: + version "1.3.8" + resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -inline-style-parser@0.2.2: - version "0.2.2" - resolved "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.2.tgz" - integrity sha512-EcKzdTHVe8wFVOGEYXiW9WmJXPjqi1T+234YpJr98RiFYKHV3cdy1+3mkTE+KHTHxFFLH51SfaGOoUdW+v7ViQ== +inline-style-parser@0.2.7: + version "0.2.7" + resolved "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz" + integrity sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA== + +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + +internmap@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz" + integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw== invariant@^2.2.4: version "2.2.4" @@ -5011,16 +5807,16 @@ invariant@^2.2.4: dependencies: loose-envify "^1.0.0" -ipaddr.js@^2.1.0: - version "2.2.0" - resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz" - integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== - ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +ipaddr.js@^2.1.0: + version "2.2.0" + resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz" + integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== + is-alphabetical@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz" @@ -5053,12 +5849,12 @@ is-ci@^3.0.1: dependencies: ci-info "^3.2.0" -is-core-module@^2.13.0: - version "2.13.1" - resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz" - integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== +is-core-module@^2.16.1: + version "2.16.1" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== dependencies: - hasown "^2.0.0" + hasown "^2.0.2" is-decimal@^2.0.0: version "2.0.1" @@ -5164,13 +5960,6 @@ is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-reference@^3.0.0: - version "3.0.2" - resolved "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz" - integrity sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg== - dependencies: - "@types/estree" "*" - is-regexp@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz" @@ -5205,16 +5994,16 @@ is-yarn-global@^0.4.0: resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz" integrity sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ== -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - isarray@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" @@ -5257,14 +6046,14 @@ jest-worker@^29.4.3: supports-color "^8.0.0" jiti@^1.20.0: - version "1.21.0" - resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz" - integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== + version "1.21.7" + resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz" + integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A== joi@^17.9.2: - version "17.12.1" - resolved "https://registry.npmjs.org/joi/-/joi-17.12.1.tgz" - integrity sha512-vtxmq+Lsc5SlfqotnfVjlViWfOL9nt/avKNbKYizwf6gsCfq9NYY/ceYRMFD8XDdrjJ9abJyScWmhmIiy+XRtQ== + version "17.13.3" + resolved "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz" + integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA== dependencies: "@hapi/hoek" "^9.3.0" "@hapi/topo" "^5.1.0" @@ -5278,24 +6067,24 @@ joi@^17.9.2: integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + version "3.14.2" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz" + integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg== dependencies: argparse "^1.0.7" esprima "^4.0.0" js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + version "4.1.1" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== dependencies: argparse "^2.0.1" -jsesc@^3.0.2, jsesc@~3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz" - integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== +jsesc@^3.0.2, jsesc@~3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== json-buffer@3.0.1: version "3.0.1" @@ -5336,6 +6125,13 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +katex@^0.16.22: + version "0.16.25" + resolved "https://registry.npmjs.org/katex/-/katex-0.16.25.tgz" + integrity sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q== + dependencies: + commander "^8.3.0" + keyv@^4.5.3: version "4.5.4" resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz" @@ -5343,6 +6139,11 @@ keyv@^4.5.3: dependencies: json-buffer "3.0.1" +khroma@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz" + integrity sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw== + kind-of@^6.0.0, kind-of@^6.0.2: version "6.0.3" resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" @@ -5353,6 +6154,22 @@ kleur@^3.0.3: resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +kolorist@^1.8.0: + version "1.8.0" + resolved "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz" + integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ== + +langium@3.3.1: + version "3.3.1" + resolved "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz" + integrity sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w== + dependencies: + chevrotain "~11.0.3" + chevrotain-allstar "~0.3.0" + vscode-languageserver "~9.0.1" + vscode-languageserver-textdocument "~1.0.11" + vscode-uri "~3.0.8" + latest-version@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz" @@ -5361,22 +6178,32 @@ latest-version@^7.0.0: package-json "^8.1.0" launch-editor@^2.6.1: - version "2.11.1" - resolved "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz" - integrity sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg== + version "2.12.0" + resolved "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz" + integrity sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg== dependencies: picocolors "^1.1.1" shell-quote "^1.8.3" +layout-base@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz" + integrity sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg== + +layout-base@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz" + integrity sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg== + leven@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== lilconfig@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz" - integrity sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ== + version "3.1.3" + resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz" + integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== lines-and-columns@^1.1.6: version "1.2.4" @@ -5397,6 +6224,15 @@ loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" +local-pkg@^1.1.1: + version "1.1.2" + resolved "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz" + integrity sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A== + dependencies: + mlly "^1.7.4" + pkg-types "^2.3.0" + quansync "^0.2.11" + locate-path@^7.1.0: version "7.2.0" resolved "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz" @@ -5404,6 +6240,11 @@ locate-path@^7.1.0: dependencies: p-locate "^6.0.0" +lodash-es@4.17.21, lodash-es@^4.17.21: + version "4.17.21" + resolved "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" @@ -5475,22 +6316,28 @@ markdown-table@^2.0.0: repeat-string "^1.0.0" markdown-table@^3.0.0: - version "3.0.3" - resolved "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz" - integrity sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw== + version "3.0.4" + resolved "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz" + integrity sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw== + +marked@^16.2.1, marked@^16.3.0: + version "16.4.2" + resolved "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz" + integrity sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA== -marked@^16.3.0: - version "16.3.0" - resolved "https://registry.npmjs.org/marked/-/marked-16.3.0.tgz" - integrity sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w== +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== mdast-util-directive@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.0.0.tgz" - integrity sha512-JUpYOqKI4mM3sZcNxmF/ox04XYFFkNwr0CFlrQIkCwbvH0xzMCqkMqAde9wRd80VAhaUrwFwKm2nxretdT1h7Q== + version "3.1.0" + resolved "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz" + integrity sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q== dependencies: "@types/mdast" "^4.0.0" "@types/unist" "^3.0.0" + ccount "^2.0.0" devlop "^1.0.0" mdast-util-from-markdown "^2.0.0" mdast-util-to-markdown "^2.0.0" @@ -5499,9 +6346,9 @@ mdast-util-directive@^3.0.0: unist-util-visit-parents "^6.0.0" mdast-util-find-and-replace@^3.0.0, mdast-util-find-and-replace@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz" - integrity sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA== + version "3.0.2" + resolved "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz" + integrity sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg== dependencies: "@types/mdast" "^4.0.0" escape-string-regexp "^5.0.0" @@ -5509,9 +6356,9 @@ mdast-util-find-and-replace@^3.0.0, mdast-util-find-and-replace@^3.0.1: unist-util-visit-parents "^6.0.0" mdast-util-from-markdown@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.0.tgz" - integrity sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA== + version "2.0.2" + resolved "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz" + integrity sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA== dependencies: "@types/mdast" "^4.0.0" "@types/unist" "^3.0.0" @@ -5539,9 +6386,9 @@ mdast-util-frontmatter@^2.0.0: micromark-extension-frontmatter "^2.0.0" mdast-util-gfm-autolink-literal@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.0.tgz" - integrity sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg== + version "2.0.1" + resolved "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz" + integrity sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ== dependencies: "@types/mdast" "^4.0.0" ccount "^2.0.0" @@ -5550,9 +6397,9 @@ mdast-util-gfm-autolink-literal@^2.0.0: micromark-util-character "^2.0.0" mdast-util-gfm-footnote@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz" - integrity sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ== + version "2.1.0" + resolved "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz" + integrity sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ== dependencies: "@types/mdast" "^4.0.0" devlop "^1.1.0" @@ -5591,9 +6438,9 @@ mdast-util-gfm-task-list-item@^2.0.0: mdast-util-to-markdown "^2.0.0" mdast-util-gfm@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz" - integrity sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw== + version "3.1.0" + resolved "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz" + integrity sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ== dependencies: mdast-util-from-markdown "^2.0.0" mdast-util-gfm-autolink-literal "^2.0.0" @@ -5604,9 +6451,9 @@ mdast-util-gfm@^3.0.0: mdast-util-to-markdown "^2.0.0" mdast-util-mdx-expression@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz" - integrity sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw== + version "2.0.1" + resolved "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz" + integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ== dependencies: "@types/estree-jsx" "^1.0.0" "@types/hast" "^3.0.0" @@ -5616,9 +6463,9 @@ mdast-util-mdx-expression@^2.0.0: mdast-util-to-markdown "^2.0.0" mdast-util-mdx-jsx@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.0.tgz" - integrity sha512-A8AJHlR7/wPQ3+Jre1+1rq040fX9A4Q1jG8JxmSNp/PLPHg80A6475wxTp3KzHpApFH6yWxFotHrJQA3dXP6/w== + version "3.2.0" + resolved "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz" + integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q== dependencies: "@types/estree-jsx" "^1.0.0" "@types/hast" "^3.0.0" @@ -5630,7 +6477,6 @@ mdast-util-mdx-jsx@^3.0.0: mdast-util-to-markdown "^2.0.0" parse-entities "^4.0.0" stringify-entities "^4.0.0" - unist-util-remove-position "^5.0.0" unist-util-stringify-position "^4.0.0" vfile-message "^4.0.0" @@ -5666,9 +6512,9 @@ mdast-util-phrasing@^4.0.0: unist-util-is "^6.0.0" mdast-util-to-hast@^13.0.0: - version "13.1.0" - resolved "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.1.0.tgz" - integrity sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA== + version "13.2.1" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz#d7ff84ca499a57e2c060ae67548ad950e689a053" + integrity sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA== dependencies: "@types/hast" "^3.0.0" "@types/mdast" "^4.0.0" @@ -5681,15 +6527,16 @@ mdast-util-to-hast@^13.0.0: vfile "^6.0.0" mdast-util-to-markdown@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz" - integrity sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ== + version "2.1.2" + resolved "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz" + integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA== dependencies: "@types/mdast" "^4.0.0" "@types/unist" "^3.0.0" longest-streak "^3.0.0" mdast-util-phrasing "^4.0.0" mdast-util-to-string "^4.0.0" + micromark-util-classify-character "^2.0.0" micromark-util-decode-string "^2.0.0" unist-util-visit "^5.0.0" zwitch "^2.0.0" @@ -5717,9 +6564,9 @@ media-typer@0.3.0: integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== memfs@^4.43.1: - version "4.46.1" - resolved "https://registry.npmjs.org/memfs/-/memfs-4.46.1.tgz" - integrity sha512-2wjHDg7IjP+ufAqqqSxjiNePFDrvWviA79ajUwG9lkHhk3HzZOLBzzoUG8cx9vCagj6VvBQD7oXuLuFz5LaAOQ== + version "4.51.0" + resolved "https://registry.npmjs.org/memfs/-/memfs-4.51.0.tgz" + integrity sha512-4zngfkVM/GpIhC8YazOsM6E8hoB33NP0BCESPOA6z7qaL6umPJNqkO8CNYaLV2FB2MV6H1O3x2luHHOSqppv+A== dependencies: "@jsonjoy.com/json-pack" "^1.11.0" "@jsonjoy.com/util" "^1.9.0" @@ -5743,15 +6590,41 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +mermaid@>=11.6.0: + version "11.12.1" + resolved "https://registry.npmjs.org/mermaid/-/mermaid-11.12.1.tgz" + integrity sha512-UlIZrRariB11TY1RtTgUWp65tphtBv4CSq7vyS2ZZ2TgoMjs2nloq+wFqxiwcxlhHUvs7DPGgMjs2aeQxz5h9g== + dependencies: + "@braintree/sanitize-url" "^7.1.1" + "@iconify/utils" "^3.0.1" + "@mermaid-js/parser" "^0.6.3" + "@types/d3" "^7.4.3" + cytoscape "^3.29.3" + cytoscape-cose-bilkent "^4.1.0" + cytoscape-fcose "^2.2.0" + d3 "^7.9.0" + d3-sankey "^0.12.3" + dagre-d3-es "7.0.13" + dayjs "^1.11.18" + dompurify "^3.2.5" + katex "^0.16.22" + khroma "^2.1.0" + lodash-es "^4.17.21" + marked "^16.2.1" + roughjs "^4.6.6" + stylis "^4.3.6" + ts-dedent "^2.2.0" + uuid "^11.1.0" + methods@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== micromark-core-commonmark@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.0.tgz" - integrity sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA== + version "2.0.3" + resolved "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz" + integrity sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg== dependencies: decode-named-character-reference "^1.0.0" devlop "^1.0.0" @@ -5771,9 +6644,9 @@ micromark-core-commonmark@^2.0.0: micromark-util-types "^2.0.0" micromark-extension-directive@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.0.tgz" - integrity sha512-61OI07qpQrERc+0wEysLHMvoiO3s2R56x5u7glHq2Yqq6EHbH4dW25G9GfDdGCDYqA21KE6DWgNSzxSwHc2hSg== + version "3.0.2" + resolved "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz" + integrity sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA== dependencies: devlop "^1.0.0" micromark-factory-space "^2.0.0" @@ -5794,9 +6667,9 @@ micromark-extension-frontmatter@^2.0.0: micromark-util-types "^2.0.0" micromark-extension-gfm-autolink-literal@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.0.0.tgz" - integrity sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg== + version "2.1.0" + resolved "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz" + integrity sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw== dependencies: micromark-util-character "^2.0.0" micromark-util-sanitize-uri "^2.0.0" @@ -5804,9 +6677,9 @@ micromark-extension-gfm-autolink-literal@^2.0.0: micromark-util-types "^2.0.0" micromark-extension-gfm-footnote@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.0.0.tgz" - integrity sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg== + version "2.1.0" + resolved "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz" + integrity sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw== dependencies: devlop "^1.0.0" micromark-core-commonmark "^2.0.0" @@ -5818,9 +6691,9 @@ micromark-extension-gfm-footnote@^2.0.0: micromark-util-types "^2.0.0" micromark-extension-gfm-strikethrough@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.0.0.tgz" - integrity sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw== + version "2.1.0" + resolved "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz" + integrity sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw== dependencies: devlop "^1.0.0" micromark-util-chunked "^2.0.0" @@ -5830,9 +6703,9 @@ micromark-extension-gfm-strikethrough@^2.0.0: micromark-util-types "^2.0.0" micromark-extension-gfm-table@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.0.0.tgz" - integrity sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw== + version "2.1.1" + resolved "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz" + integrity sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg== dependencies: devlop "^1.0.0" micromark-factory-space "^2.0.0" @@ -5848,9 +6721,9 @@ micromark-extension-gfm-tagfilter@^2.0.0: micromark-util-types "^2.0.0" micromark-extension-gfm-task-list-item@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.0.1.tgz" - integrity sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw== + version "2.1.0" + resolved "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz" + integrity sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw== dependencies: devlop "^1.0.0" micromark-factory-space "^2.0.0" @@ -5873,9 +6746,9 @@ micromark-extension-gfm@^3.0.0: micromark-util-types "^2.0.0" micromark-extension-mdx-expression@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.0.tgz" - integrity sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ== + version "3.0.1" + resolved "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz" + integrity sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q== dependencies: "@types/estree" "^1.0.0" devlop "^1.0.0" @@ -5887,17 +6760,17 @@ micromark-extension-mdx-expression@^3.0.0: micromark-util-types "^2.0.0" micromark-extension-mdx-jsx@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.0.tgz" - integrity sha512-uvhhss8OGuzR4/N17L1JwvmJIpPhAd8oByMawEKx6NVdBCbesjH4t+vjEp3ZXft9DwvlKSD07fCeI44/N0Vf2w== + version "3.0.2" + resolved "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz" + integrity sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ== dependencies: - "@types/acorn" "^4.0.0" "@types/estree" "^1.0.0" devlop "^1.0.0" estree-util-is-identifier-name "^3.0.0" micromark-factory-mdx-expression "^2.0.0" micromark-factory-space "^2.0.0" micromark-util-character "^2.0.0" + micromark-util-events-to-acorn "^2.0.0" micromark-util-symbol "^2.0.0" micromark-util-types "^2.0.0" vfile-message "^4.0.0" @@ -5939,18 +6812,18 @@ micromark-extension-mdxjs@^3.0.0: micromark-util-types "^2.0.0" micromark-factory-destination@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz" - integrity sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA== + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz" + integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA== dependencies: micromark-util-character "^2.0.0" micromark-util-symbol "^2.0.0" micromark-util-types "^2.0.0" micromark-factory-label@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz" - integrity sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw== + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz" + integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg== dependencies: devlop "^1.0.0" micromark-util-character "^2.0.0" @@ -5958,12 +6831,13 @@ micromark-factory-label@^2.0.0: micromark-util-types "^2.0.0" micromark-factory-mdx-expression@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.1.tgz" - integrity sha512-F0ccWIUHRLRrYp5TC9ZYXmZo+p2AM13ggbsW4T0b5CRKP8KHVRB8t4pwtBgTxtjRmwrK0Irwm7vs2JOZabHZfg== + version "2.0.3" + resolved "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz" + integrity sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ== dependencies: "@types/estree" "^1.0.0" devlop "^1.0.0" + micromark-factory-space "^2.0.0" micromark-util-character "^2.0.0" micromark-util-events-to-acorn "^2.0.0" micromark-util-symbol "^2.0.0" @@ -5980,17 +6854,17 @@ micromark-factory-space@^1.0.0: micromark-util-types "^1.0.0" micromark-factory-space@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz" - integrity sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg== + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz" + integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg== dependencies: micromark-util-character "^2.0.0" micromark-util-types "^2.0.0" micromark-factory-title@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz" - integrity sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A== + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz" + integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw== dependencies: micromark-factory-space "^2.0.0" micromark-util-character "^2.0.0" @@ -5998,9 +6872,9 @@ micromark-factory-title@^2.0.0: micromark-util-types "^2.0.0" micromark-factory-whitespace@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz" - integrity sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA== + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz" + integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ== dependencies: micromark-factory-space "^2.0.0" micromark-util-character "^2.0.0" @@ -6016,48 +6890,48 @@ micromark-util-character@^1.0.0, micromark-util-character@^1.1.0: micromark-util-types "^1.0.0" micromark-util-character@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz" - integrity sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ== + version "2.1.1" + resolved "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz" + integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== dependencies: micromark-util-symbol "^2.0.0" micromark-util-types "^2.0.0" micromark-util-chunked@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz" - integrity sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg== + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz" + integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA== dependencies: micromark-util-symbol "^2.0.0" micromark-util-classify-character@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz" - integrity sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw== + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz" + integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q== dependencies: micromark-util-character "^2.0.0" micromark-util-symbol "^2.0.0" micromark-util-types "^2.0.0" micromark-util-combine-extensions@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz" - integrity sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ== + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz" + integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg== dependencies: micromark-util-chunked "^2.0.0" micromark-util-types "^2.0.0" micromark-util-decode-numeric-character-reference@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz" - integrity sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ== + version "2.0.2" + resolved "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz" + integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw== dependencies: micromark-util-symbol "^2.0.0" micromark-util-decode-string@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz" - integrity sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA== + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz" + integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ== dependencies: decode-named-character-reference "^1.0.0" micromark-util-character "^2.0.0" @@ -6065,16 +6939,15 @@ micromark-util-decode-string@^2.0.0: micromark-util-symbol "^2.0.0" micromark-util-encode@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz" - integrity sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA== + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz" + integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== micromark-util-events-to-acorn@^2.0.0: - version "2.0.2" - resolved "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.2.tgz" - integrity sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA== + version "2.0.3" + resolved "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz" + integrity sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg== dependencies: - "@types/acorn" "^4.0.0" "@types/estree" "^1.0.0" "@types/unist" "^3.0.0" devlop "^1.0.0" @@ -6084,37 +6957,37 @@ micromark-util-events-to-acorn@^2.0.0: vfile-message "^4.0.0" micromark-util-html-tag-name@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz" - integrity sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw== + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz" + integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA== micromark-util-normalize-identifier@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz" - integrity sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w== + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz" + integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q== dependencies: micromark-util-symbol "^2.0.0" micromark-util-resolve-all@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz" - integrity sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA== + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz" + integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg== dependencies: micromark-util-types "^2.0.0" micromark-util-sanitize-uri@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz" - integrity sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw== + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz" + integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== dependencies: micromark-util-character "^2.0.0" micromark-util-encode "^2.0.0" micromark-util-symbol "^2.0.0" micromark-util-subtokenize@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.0.tgz" - integrity sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg== + version "2.1.0" + resolved "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz" + integrity sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA== dependencies: devlop "^1.0.0" micromark-util-chunked "^2.0.0" @@ -6127,9 +7000,9 @@ micromark-util-symbol@^1.0.0, micromark-util-symbol@^1.0.1: integrity sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag== micromark-util-symbol@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz" - integrity sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw== + version "2.0.1" + resolved "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz" + integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== micromark-util-types@^1.0.0: version "1.1.0" @@ -6137,14 +7010,14 @@ micromark-util-types@^1.0.0: integrity sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg== micromark-util-types@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz" - integrity sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w== + version "2.0.2" + resolved "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz" + integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA== micromark@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz" - integrity sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ== + version "4.0.2" + resolved "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz" + integrity sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA== dependencies: "@types/debug" "^4.0.0" debug "^4.0.0" @@ -6164,7 +7037,7 @@ micromark@^4.0.0: micromark-util-symbol "^2.0.0" micromark-util-types "^2.0.0" -micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: +micromatch@^4.0.2, micromatch@^4.0.5, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -6172,60 +7045,41 @@ micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: braces "^3.0.3" picomatch "^2.3.1" +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + mime-db@^1.54.0: version "1.54.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz" integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== -"mime-db@>= 1.43.0 < 2": - version "1.52.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - mime-db@~1.33.0: version "1.33.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz" integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.27: - version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime-types@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz" - integrity sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA== - dependencies: - mime-db "^1.54.0" - -mime-types@~2.1.17, mime-types@2.1.18: +mime-types@2.1.18: version "2.1.18" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz" integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== dependencies: mime-db "~1.33.0" -mime-types@~2.1.24: +mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" -mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== +mime-types@^3.0.1: + version "3.0.2" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz" + integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== dependencies: - mime-db "1.52.0" + mime-db "^1.54.0" mime@1.6.0: version "1.6.0" @@ -6248,9 +7102,9 @@ mimic-response@^4.0.0: integrity sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg== mini-css-extract-plugin@^2.9.2: - version "2.9.2" - resolved "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz" - integrity sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w== + version "2.9.4" + resolved "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz" + integrity sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ== dependencies: schema-utils "^4.0.0" tapable "^2.2.1" @@ -6267,11 +7121,28 @@ minimatch@3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@^9.0.3: + version "9.0.5" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.0: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +mlly@^1.7.4: + version "1.8.0" + resolved "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz" + integrity sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g== + dependencies: + acorn "^8.15.0" + pathe "^2.0.3" + pkg-types "^1.3.1" + ufo "^1.6.1" + mrmime@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz" @@ -6282,12 +7153,7 @@ ms@2.0.0: resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3: +ms@2.1.3, ms@^2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -6310,6 +7176,11 @@ negotiator@0.6.3: resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +negotiator@~0.6.4: + version "0.6.4" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz" + integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== + neo-async@^2.6.2: version "2.6.2" resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" @@ -6324,9 +7195,9 @@ no-case@^3.0.4: tslib "^2.0.3" node-emoji@^2.1.0: - version "2.1.3" - resolved "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz" - integrity sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA== + version "2.2.0" + resolved "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz" + integrity sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw== dependencies: "@sindresorhus/is" "^4.6.0" char-regex "^1.0.2" @@ -6334,14 +7205,14 @@ node-emoji@^2.1.0: skin-tone "^2.0.0" node-forge@^1: - version "1.3.1" - resolved "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz" - integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + version "1.3.2" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.2.tgz#d0d2659a26eef778bf84d73e7f55c08144ee7750" + integrity sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw== -node-releases@^2.0.19: - version "2.0.19" - resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz" - integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== +node-releases@^2.0.27: + version "2.0.27" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz" + integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -6390,10 +7261,10 @@ object-assign@^4.1.1: resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.13.1: - version "1.13.1" - resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz" - integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== object-keys@^1.1.1: version "1.1.1" @@ -6401,13 +7272,15 @@ object-keys@^1.1.1: integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== object.assign@^4.1.0: - version "4.1.5" - resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz" - integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== + version "4.1.7" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz" + integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== dependencies: - call-bind "^1.0.5" + call-bind "^1.0.8" + call-bound "^1.0.3" define-properties "^1.2.1" - has-symbols "^1.0.3" + es-object-atoms "^1.0.0" + has-symbols "^1.1.0" object-keys "^1.1.1" obuf@^1.0.0, obuf@^1.1.2: @@ -6415,17 +7288,17 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -on-finished@^2.4.1, on-finished@2.4.1: +on-finished@2.4.1, on-finished@^2.4.1: version "2.4.1" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== dependencies: ee-first "1.1.1" -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== +on-headers@~1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz" + integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== onetime@^5.1.2: version "5.1.2" @@ -6523,6 +7396,11 @@ package-json@^8.1.0: registry-url "^6.0.0" semver "^7.3.7" +package-manager-detector@^1.3.0: + version "1.5.0" + resolved "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.5.0.tgz" + integrity sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw== + param-case@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz" @@ -6539,12 +7417,11 @@ parent-module@^1.0.0: callsites "^3.0.0" parse-entities@^4.0.0: - version "4.0.1" - resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz" - integrity sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w== + version "4.0.2" + resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz" + integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw== dependencies: "@types/unist" "^2.0.0" - character-entities "^2.0.0" character-entities-legacy "^3.0.0" character-reference-invalid "^2.0.0" decode-named-character-reference "^1.0.0" @@ -6568,19 +7445,19 @@ parse-numeric-range@^1.3.0: integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== parse5-htmlparser2-tree-adapter@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz" - integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g== + version "7.1.0" + resolved "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz" + integrity sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g== dependencies: - domhandler "^5.0.2" + domhandler "^5.0.3" parse5 "^7.0.0" parse5@^7.0.0: - version "7.1.2" - resolved "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz" - integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + version "7.3.0" + resolved "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz" + integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== dependencies: - entities "^4.4.0" + entities "^6.0.0" parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" @@ -6595,6 +7472,11 @@ pascal-case@^3.1.2: no-case "^3.0.4" tslib "^2.0.3" +path-data-parser@0.1.0, path-data-parser@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz" + integrity sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w== + path-exists@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz" @@ -6615,13 +7497,6 @@ path-parse@^1.0.7: resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - path-to-regexp@0.1.12: version "0.1.12" resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz" @@ -6632,19 +7507,22 @@ path-to-regexp@3.3.0: resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz" integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw== +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + path-type@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -periscopic@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz" - integrity sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw== - dependencies: - "@types/estree" "^1.0.0" - estree-walker "^3.0.0" - is-reference "^3.0.0" +pathe@^2.0.1, pathe@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz" + integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== picocolors@^1.0.0, picocolors@^1.1.1: version "1.1.1" @@ -6663,6 +7541,37 @@ pkg-dir@^7.0.0: dependencies: find-up "^6.3.0" +pkg-types@^1.3.1: + version "1.3.1" + resolved "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz" + integrity sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ== + dependencies: + confbox "^0.1.8" + mlly "^1.7.4" + pathe "^2.0.1" + +pkg-types@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz" + integrity sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig== + dependencies: + confbox "^0.2.2" + exsolve "^1.0.7" + pathe "^2.0.3" + +points-on-curve@0.2.0, points-on-curve@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz" + integrity sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A== + +points-on-path@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz" + integrity sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g== + dependencies: + path-data-parser "0.1.0" + points-on-curve "0.2.0" + postcss-attribute-case-insensitive@^7.0.1: version "7.0.1" resolved "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz" @@ -6685,15 +7594,15 @@ postcss-clamp@^4.1.0: dependencies: postcss-value-parser "^4.2.0" -postcss-color-functional-notation@^7.0.10: - version "7.0.10" - resolved "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.10.tgz" - integrity sha512-k9qX+aXHBiLTRrWoCJuUFI6F1iF6QJQUXNVWJVSbqZgj57jDhBlOvD8gNUGl35tgqDivbGLhZeW3Ongz4feuKA== +postcss-color-functional-notation@^7.0.12: + version "7.0.12" + resolved "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.12.tgz" + integrity sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw== dependencies: - "@csstools/css-color-parser" "^3.0.10" + "@csstools/css-color-parser" "^3.1.0" "@csstools/css-parser-algorithms" "^3.0.5" "@csstools/css-tokenizer" "^3.0.4" - "@csstools/postcss-progressive-custom-properties" "^4.1.0" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" postcss-color-hex-alpha@^10.0.0: @@ -6795,12 +7704,12 @@ postcss-discard-unused@^6.0.5: dependencies: postcss-selector-parser "^6.0.16" -postcss-double-position-gradients@^6.0.2: - version "6.0.2" - resolved "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.2.tgz" - integrity sha512-7qTqnL7nfLRyJK/AHSVrrXOuvDDzettC+wGoienURV8v2svNbu6zJC52ruZtHaO6mfcagFmuTGFdzRsJKB3k5Q== +postcss-double-position-gradients@^6.0.4: + version "6.0.4" + resolved "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.4.tgz" + integrity sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g== dependencies: - "@csstools/postcss-progressive-custom-properties" "^4.1.0" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" postcss-value-parser "^4.2.0" @@ -6836,15 +7745,15 @@ postcss-image-set-function@^7.0.0: "@csstools/utilities" "^2.0.0" postcss-value-parser "^4.2.0" -postcss-lab-function@^7.0.10: - version "7.0.10" - resolved "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.10.tgz" - integrity sha512-tqs6TCEv9tC1Riq6fOzHuHcZyhg4k3gIAMB8GGY/zA1ssGdm6puHMVE7t75aOSoFg7UD2wyrFFhbldiCMyyFTQ== +postcss-lab-function@^7.0.12: + version "7.0.12" + resolved "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.12.tgz" + integrity sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w== dependencies: - "@csstools/css-color-parser" "^3.0.10" + "@csstools/css-color-parser" "^3.1.0" "@csstools/css-parser-algorithms" "^3.0.5" "@csstools/css-tokenizer" "^3.0.4" - "@csstools/postcss-progressive-custom-properties" "^4.1.0" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" postcss-loader@^7.3.4: @@ -6949,12 +7858,12 @@ postcss-modules-values@^4.0.0: dependencies: icss-utils "^5.0.0" -postcss-nesting@^13.0.1: - version "13.0.1" - resolved "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz" - integrity sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ== +postcss-nesting@^13.0.2: + version "13.0.2" + resolved "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz" + integrity sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ== dependencies: - "@csstools/selector-resolve-nested" "^3.0.0" + "@csstools/selector-resolve-nested" "^3.1.0" "@csstools/selector-specificity" "^5.0.0" postcss-selector-parser "^7.0.0" @@ -7053,24 +7962,27 @@ postcss-place@^10.0.0: postcss-value-parser "^4.2.0" postcss-preset-env@^10.2.1: - version "10.2.1" - resolved "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.2.1.tgz" - integrity sha512-mDInnlm4mYhmR0S79hNLzseW9nx4Ihd8s15K99iu6u6QhoSQgqWX9Oj6nTd/8Dz3b0T7v2JSrfnXsDfv9TFvDg== - dependencies: - "@csstools/postcss-cascade-layers" "^5.0.1" - "@csstools/postcss-color-function" "^4.0.10" - "@csstools/postcss-color-mix-function" "^3.0.10" - "@csstools/postcss-color-mix-variadic-function-arguments" "^1.0.0" - "@csstools/postcss-content-alt-text" "^2.0.6" + version "10.4.0" + resolved "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.4.0.tgz" + integrity sha512-2kqpOthQ6JhxqQq1FSAAZGe9COQv75Aw8WbsOvQVNJ2nSevc9Yx/IKZGuZ7XJ+iOTtVon7LfO7ELRzg8AZ+sdw== + dependencies: + "@csstools/postcss-alpha-function" "^1.0.1" + "@csstools/postcss-cascade-layers" "^5.0.2" + "@csstools/postcss-color-function" "^4.0.12" + "@csstools/postcss-color-function-display-p3-linear" "^1.0.1" + "@csstools/postcss-color-mix-function" "^3.0.12" + "@csstools/postcss-color-mix-variadic-function-arguments" "^1.0.2" + "@csstools/postcss-content-alt-text" "^2.0.8" + "@csstools/postcss-contrast-color-function" "^2.0.12" "@csstools/postcss-exponential-functions" "^2.0.9" "@csstools/postcss-font-format-keywords" "^4.0.0" - "@csstools/postcss-gamut-mapping" "^2.0.10" - "@csstools/postcss-gradients-interpolation-method" "^5.0.10" - "@csstools/postcss-hwb-function" "^4.0.10" - "@csstools/postcss-ic-unit" "^4.0.2" + "@csstools/postcss-gamut-mapping" "^2.0.11" + "@csstools/postcss-gradients-interpolation-method" "^5.0.12" + "@csstools/postcss-hwb-function" "^4.0.12" + "@csstools/postcss-ic-unit" "^4.0.4" "@csstools/postcss-initial" "^2.0.1" - "@csstools/postcss-is-pseudo-class" "^5.0.1" - "@csstools/postcss-light-dark-function" "^2.0.9" + "@csstools/postcss-is-pseudo-class" "^5.0.3" + "@csstools/postcss-light-dark-function" "^2.0.11" "@csstools/postcss-logical-float-and-clear" "^3.0.0" "@csstools/postcss-logical-overflow" "^2.0.0" "@csstools/postcss-logical-overscroll-behavior" "^2.0.0" @@ -7080,40 +7992,40 @@ postcss-preset-env@^10.2.1: "@csstools/postcss-media-queries-aspect-ratio-number-values" "^3.0.5" "@csstools/postcss-nested-calc" "^4.0.0" "@csstools/postcss-normalize-display-values" "^4.0.0" - "@csstools/postcss-oklab-function" "^4.0.10" - "@csstools/postcss-progressive-custom-properties" "^4.1.0" + "@csstools/postcss-oklab-function" "^4.0.12" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/postcss-random-function" "^2.0.1" - "@csstools/postcss-relative-color-syntax" "^3.0.10" + "@csstools/postcss-relative-color-syntax" "^3.0.12" "@csstools/postcss-scope-pseudo-class" "^4.0.1" "@csstools/postcss-sign-functions" "^1.1.4" "@csstools/postcss-stepped-value-functions" "^4.0.9" - "@csstools/postcss-text-decoration-shorthand" "^4.0.2" + "@csstools/postcss-text-decoration-shorthand" "^4.0.3" "@csstools/postcss-trigonometric-functions" "^4.0.9" "@csstools/postcss-unset-value" "^4.0.0" autoprefixer "^10.4.21" - browserslist "^4.25.0" + browserslist "^4.26.0" css-blank-pseudo "^7.0.1" - css-has-pseudo "^7.0.2" + css-has-pseudo "^7.0.3" css-prefers-color-scheme "^10.0.0" - cssdb "^8.3.0" + cssdb "^8.4.2" postcss-attribute-case-insensitive "^7.0.1" postcss-clamp "^4.1.0" - postcss-color-functional-notation "^7.0.10" + postcss-color-functional-notation "^7.0.12" postcss-color-hex-alpha "^10.0.0" postcss-color-rebeccapurple "^10.0.0" postcss-custom-media "^11.0.6" postcss-custom-properties "^14.0.6" postcss-custom-selectors "^8.0.5" postcss-dir-pseudo-class "^9.0.1" - postcss-double-position-gradients "^6.0.2" + postcss-double-position-gradients "^6.0.4" postcss-focus-visible "^10.0.1" postcss-focus-within "^9.0.1" postcss-font-variant "^5.0.0" postcss-gap-properties "^6.0.0" postcss-image-set-function "^7.0.0" - postcss-lab-function "^7.0.10" + postcss-lab-function "^7.0.12" postcss-logical "^8.1.0" - postcss-nesting "^13.0.1" + postcss-nesting "^13.0.2" postcss-opacity-percentage "^3.0.0" postcss-overflow-shorthand "^6.0.0" postcss-page-break "^3.0.4" @@ -7164,17 +8076,17 @@ postcss-selector-not@^8.0.1: postcss-selector-parser "^7.0.0" postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.16: - version "6.0.16" - resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz" - integrity sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw== + version "6.1.2" + resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz" + integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" postcss-selector-parser@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz" - integrity sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ== + version "7.1.0" + resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz" + integrity sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" @@ -7211,10 +8123,10 @@ postcss-zindex@^6.0.2: resolved "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz" integrity sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg== -"postcss@^7.0.0 || ^8.0.1", postcss@^8, postcss@^8.0.3, postcss@^8.0.9, postcss@^8.1.0, postcss@^8.2.2, postcss@^8.4, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.24, postcss@^8.4.31, postcss@^8.4.33, postcss@^8.4.6, postcss@^8.5.4: - version "8.5.4" - resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz" - integrity sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w== +postcss@^8.4.21, postcss@^8.4.24, postcss@^8.4.33, postcss@^8.5.4: + version "8.5.6" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== dependencies: nanoid "^3.3.11" picocolors "^1.1.1" @@ -7269,9 +8181,14 @@ prop-types@^15.6.2, prop-types@^15.7.2: react-is "^16.13.1" property-information@^6.0.0: - version "6.4.1" - resolved "https://registry.npmjs.org/property-information/-/property-information-6.4.1.tgz" - integrity sha512-OHYtXfu5aI2sS2LWFSN5rgJjrQ4pCy8i1jubJLe2QvMF8JJ++HXTUIVWFLfXJoaOfvYYjk2SN8J2wFUWIGXT4w== + version "6.5.0" + resolved "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz" + integrity sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig== + +property-information@^7.0.0: + version "7.1.0" + resolved "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz" + integrity sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ== proto-list@~1.2.1: version "1.2.4" @@ -7305,6 +8222,11 @@ qs@6.13.0: dependencies: side-channel "^1.0.6" +quansync@^0.2.11: + version "0.2.11" + resolved "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz" + integrity sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA== + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" @@ -7322,21 +8244,16 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -range-parser@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - range-parser@1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz" integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A== +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + raw-body@2.5.2: version "2.5.2" resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz" @@ -7357,10 +8274,10 @@ rc@1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-dom@*, "react-dom@^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^18.0.0 || ^19.0.0", react-dom@^19.0.0, "react-dom@>= 16.8.0 < 20.0.0": - version "19.2.0" - resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz" - integrity sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ== +react-dom@^19.0.0: + version "19.2.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.1.tgz#ce3527560bda4f997e47d10dab754825b3061f59" + integrity sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg== dependencies: scheduler "^0.27.0" @@ -7386,9 +8303,9 @@ react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== react-json-view-lite@^2.3.0: - version "2.4.1" - resolved "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-2.4.1.tgz" - integrity sha512-fwFYknRIBxjbFm0kBDrzgBy1xa5tDg2LyXXBepC5f1b+MY3BUClMCsvanMPn089JbV1Eg3nZcrp0VCuH43aXnA== + version "2.5.0" + resolved "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-2.5.0.tgz" + integrity sha512-tk7o7QG9oYyELWHL8xiMQ8x4WzjCzbWNyig3uexmkLb54r8jO0yH3WCWx8UZS0c49eSA4QUmG5caiRJ8fAn58g== react-loadable-ssr-addon-v5-slorber@^1.0.1: version "1.0.1" @@ -7397,7 +8314,7 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1: dependencies: "@babel/runtime" "^7.10.3" -react-loadable@*, "react-loadable@npm:@docusaurus/react-loadable@6.0.0": +"react-loadable@npm:@docusaurus/react-loadable@6.0.0": version "6.0.0" resolved "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz" integrity sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ== @@ -7424,7 +8341,7 @@ react-router-dom@^5.3.4: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-router@^5.3.4, react-router@>=5, react-router@5.3.4: +react-router@5.3.4, react-router@^5.3.4: version "5.3.4" resolved "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz" integrity sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA== @@ -7439,10 +8356,10 @@ react-router@^5.3.4, react-router@>=5, react-router@5.3.4: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react@*, "react@^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^18 || ^19 || ^19.0.0-rc", "react@^18.0.0 || ^19.0.0", react@^19.0.0, react@^19.2.0, "react@>= 16.8.0 < 20.0.0", react@>=15, react@>=16, react@>=16.0.0: - version "19.2.0" - resolved "https://registry.npmjs.org/react/-/react-19.2.0.tgz" - integrity sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ== +react@^19.2.0: + version "19.2.1" + resolved "https://registry.yarnpkg.com/react/-/react-19.2.1.tgz#8600fa205e58e2e807f6ef431c9f6492591a2700" + integrity sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw== readable-stream@^2.0.1: version "2.3.8" @@ -7473,10 +8390,50 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -regenerate-unicode-properties@^10.2.0: - version "10.2.0" - resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz" - integrity sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA== +recma-build-jsx@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz" + integrity sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew== + dependencies: + "@types/estree" "^1.0.0" + estree-util-build-jsx "^3.0.0" + vfile "^6.0.0" + +recma-jsx@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz" + integrity sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w== + dependencies: + acorn-jsx "^5.0.0" + estree-util-to-js "^2.0.0" + recma-parse "^1.0.0" + recma-stringify "^1.0.0" + unified "^11.0.0" + +recma-parse@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz" + integrity sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ== + dependencies: + "@types/estree" "^1.0.0" + esast-util-from-js "^2.0.0" + unified "^11.0.0" + vfile "^6.0.0" + +recma-stringify@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz" + integrity sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g== + dependencies: + "@types/estree" "^1.0.0" + estree-util-to-js "^2.0.0" + unified "^11.0.0" + vfile "^6.0.0" + +regenerate-unicode-properties@^10.2.2: + version "10.2.2" + resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz" + integrity sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g== dependencies: regenerate "^1.4.2" @@ -7490,24 +8447,17 @@ regenerator-runtime@^0.14.0: resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz" integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== -regenerator-transform@^0.15.2: - version "0.15.2" - resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz" - integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== - dependencies: - "@babel/runtime" "^7.8.4" - -regexpu-core@^6.1.1: - version "6.1.1" - resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz" - integrity sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw== +regexpu-core@^6.3.1: + version "6.4.0" + resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz" + integrity sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA== dependencies: regenerate "^1.4.2" - regenerate-unicode-properties "^10.2.0" + regenerate-unicode-properties "^10.2.2" regjsgen "^0.8.0" - regjsparser "^0.11.0" + regjsparser "^0.13.0" unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.1.0" + unicode-match-property-value-ecmascript "^2.2.1" registry-auth-token@^5.0.1: version "5.0.2" @@ -7528,12 +8478,12 @@ regjsgen@^0.8.0: resolved "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz" integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q== -regjsparser@^0.11.0: - version "0.11.2" - resolved "https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.2.tgz" - integrity sha512-3OGZZ4HoLJkkAZx/48mTXJNlmqTGOzc0o9OWQPuWpkOlXXPbyN6OafCcoXUnBqE2D3f/T5L+pWc1kdEmnfnRsA== +regjsparser@^0.13.0: + version "0.13.0" + resolved "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz" + integrity sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q== dependencies: - jsesc "~3.0.2" + jsesc "~3.1.0" rehype-raw@^7.0.0: version "7.0.0" @@ -7544,15 +8494,24 @@ rehype-raw@^7.0.0: hast-util-raw "^9.0.0" vfile "^6.0.0" +rehype-recma@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz" + integrity sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw== + dependencies: + "@types/estree" "^1.0.0" + "@types/hast" "^3.0.0" + hast-util-to-estree "^3.0.0" + relateurl@^0.2.7: version "0.2.7" resolved "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz" integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== remark-directive@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.0.tgz" - integrity sha512-l1UyWJ6Eg1VPU7Hm/9tt0zKtReJQNOA4+iDMAxTyZNWnJnFlbS/7zhiel/rogTLQ2vMYwDzSJa4BiVNqGlqIMA== + version "3.0.1" + resolved "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz" + integrity sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A== dependencies: "@types/mdast" "^4.0.0" mdast-util-directive "^3.0.0" @@ -7581,9 +8540,9 @@ remark-frontmatter@^5.0.0: unified "^11.0.0" remark-gfm@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz" - integrity sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA== + version "4.0.1" + resolved "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz" + integrity sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg== dependencies: "@types/mdast" "^4.0.0" mdast-util-gfm "^3.0.0" @@ -7593,9 +8552,9 @@ remark-gfm@^4.0.0: unified "^11.0.0" remark-mdx@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.0.1.tgz" - integrity sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA== + version "3.1.1" + resolved "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz" + integrity sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg== dependencies: mdast-util-mdx "^3.0.0" micromark-extension-mdxjs "^3.0.0" @@ -7611,9 +8570,9 @@ remark-parse@^11.0.0: unified "^11.0.0" remark-rehype@^11.0.0: - version "11.1.0" - resolved "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.0.tgz" - integrity sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g== + version "11.1.2" + resolved "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz" + integrity sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw== dependencies: "@types/hast" "^3.0.0" "@types/mdast" "^4.0.0" @@ -7676,12 +8635,12 @@ resolve-pathname@^3.0.0: resolved "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz" integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== -resolve@^1.14.2: - version "1.22.8" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== +resolve@^1.22.10: + version "1.22.11" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz" + integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== dependencies: - is-core-module "^2.13.0" + is-core-module "^2.16.1" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -7698,14 +8657,29 @@ retry@^0.13.1: integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + version "1.1.0" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + +robust-predicates@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz" + integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg== + +roughjs@^4.6.6: + version "4.6.6" + resolved "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz" + integrity sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ== + dependencies: + hachure-fill "^0.5.2" + path-data-parser "^0.1.0" + points-on-curve "^0.2.0" + points-on-path "^0.2.1" rtlcss@^4.1.0: - version "4.1.1" - resolved "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz" - integrity sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ== + version "4.3.0" + resolved "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz" + integrity sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig== dependencies: escalade "^3.1.1" picocolors "^1.0.0" @@ -7724,7 +8698,12 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -safe-buffer@^5.1.0, safe-buffer@>=5.1.0, safe-buffer@~5.2.0, safe-buffer@5.2.1: +rw@1: + version "1.3.3" + resolved "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz" + integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== + +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -7734,24 +8713,19 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.1.2: - version "5.1.2" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== sax@^1.2.4: - version "1.3.0" - resolved "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz" - integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA== + version "1.4.3" + resolved "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz" + integrity sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ== scheduler@^0.27.0: version "0.27.0" - resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd" integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== schema-dts@^1.1.2: @@ -7759,25 +8733,7 @@ schema-dts@^1.1.2: resolved "https://registry.npmjs.org/schema-dts/-/schema-dts-1.1.5.tgz" integrity sha512-RJr9EaCmsLzBX2NDiO5Z3ux2BVosNZN5jo0gWgsyKvxKIUL5R3swNvoorulAeL9kLB0iTSX7V6aokhla2m7xbg== -schema-utils@^3.0.0: - version "3.3.0" - resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz" - integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^3.1.1: - version "3.3.0" - resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz" - integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^3.2.0: +schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0: version "3.3.0" resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz" integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== @@ -7787,20 +8743,15 @@ schema-utils@^3.2.0: ajv-keywords "^3.5.2" schema-utils@^4.0.0, schema-utils@^4.0.1, schema-utils@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz" - integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== + version "4.3.3" + resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz" + integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA== dependencies: "@types/json-schema" "^7.0.9" ajv "^8.9.0" ajv-formats "^2.1.1" ajv-keywords "^5.1.0" -"search-insights@>= 1 < 3": - version "2.17.3" - resolved "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz" - integrity sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ== - section-matter@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz" @@ -7903,17 +8854,17 @@ serve-static@1.16.2: parseurl "~1.3.3" send "0.19.0" -set-function-length@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz" - integrity sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g== +set-function-length@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== dependencies: - define-data-property "^1.1.2" + define-data-property "^1.1.4" es-errors "^1.3.0" function-bind "^1.1.2" - get-intrinsic "^1.2.3" + get-intrinsic "^1.2.4" gopd "^1.0.1" - has-property-descriptors "^1.0.1" + has-property-descriptors "^1.0.2" setprototypeof@1.1.0: version "1.1.0" @@ -7954,15 +8905,45 @@ shell-quote@^1.8.3: resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz" integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + version "1.1.0" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== dependencies: - call-bind "^1.0.7" es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.7" @@ -7984,9 +8965,9 @@ sisteransi@^1.0.5: integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== sitemap@^7.1.1: - version "7.1.1" - resolved "https://registry.npmjs.org/sitemap/-/sitemap-7.1.1.tgz" - integrity sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg== + version "7.1.2" + resolved "https://registry.npmjs.org/sitemap/-/sitemap-7.1.2.tgz" + integrity sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw== dependencies: "@types/node" "^17.0.5" "@types/sax" "^1.2.1" @@ -8045,20 +9026,15 @@ source-map-support@~0.5.20: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.6.0: +source-map@^0.6.0, source-map@~0.6.0: version "0.6.1" resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== source-map@^0.7.0: - version "0.7.4" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz" - integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== - -source-map@~0.6.0: - version "0.6.1" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + version "0.7.6" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz" + integrity sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ== space-separated-tokens@^2.0.0: version "2.0.2" @@ -8098,45 +9074,22 @@ srcset@^4.0.0: resolved "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz" integrity sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw== -"statuses@>= 1.4.0 < 2": - version "1.5.0" - resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - statuses@2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -std-env@^3.7.0: - version "3.7.0" - resolved "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz" - integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== -string-width@^4.1.0: - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" +std-env@^3.7.0: + version "3.10.0" + resolved "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz" + integrity sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg== -string-width@^4.2.0: +string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -8154,10 +9107,24 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + stringify-entities@^4.0.0: - version "4.0.3" - resolved "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz" - integrity sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g== + version "4.0.4" + resolved "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz" + integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg== dependencies: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" @@ -8205,19 +9172,19 @@ strip-json-comments@~2.0.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== -style-to-object@^0.4.0: - version "0.4.4" - resolved "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz" - integrity sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg== +style-to-js@^1.0.0: + version "1.1.21" + resolved "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz" + integrity sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ== dependencies: - inline-style-parser "0.1.1" + style-to-object "1.0.14" -style-to-object@^1.0.0: - version "1.0.5" - resolved "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.5.tgz" - integrity sha512-rDRwHtoDD3UMMrmZ6BzOW0naTjMsVZLIjsGleSKS/0Oz+cgCfAPRspaqJuE8rDzpKha/nEvnM0IF4seEAZUTKQ== +style-to-object@1.0.14: + version "1.0.14" + resolved "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz" + integrity sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw== dependencies: - inline-style-parser "0.2.2" + inline-style-parser "0.2.7" stylehacks@^6.1.1: version "6.1.1" @@ -8227,6 +9194,11 @@ stylehacks@^6.1.1: browserslist "^4.23.0" postcss-selector-parser "^6.0.16" +stylis@^4.3.6: + version "4.3.6" + resolved "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz" + integrity sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ== + supports-color@^7.1.0: version "7.2.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" @@ -8252,9 +9224,9 @@ svg-parser@^2.0.4: integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== svgo@^3.0.2, svgo@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz" - integrity sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ== + version "3.3.2" + resolved "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz" + integrity sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw== dependencies: "@trysound/sax" "0.2.0" commander "^7.2.0" @@ -8323,6 +9295,11 @@ tiny-warning@^1.0.0: resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== +tinyexec@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz" + integrity sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg== + tinypool@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz" @@ -8345,7 +9322,7 @@ totalist@^3.0.0: resolved "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz" integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== -tree-dump@^1.0.3: +tree-dump@^1.0.3, tree-dump@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz" integrity sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA== @@ -8360,7 +9337,12 @@ trough@^2.0.0: resolved "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz" integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== -tslib@^2, tslib@^2.0.0, tslib@^2.0.3, tslib@^2.6.0, tslib@2: +ts-dedent@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz" + integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ== + +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.6.0: version "2.6.2" resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -8395,20 +9377,25 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@>=4.9.5, typescript@~5.9.0: +typescript@~5.9.0: version "5.9.3" - resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== +ufo@^1.6.1: + version "1.6.1" + resolved "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz" + integrity sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA== + undici-types@~5.26.4: version "5.26.5" resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== unicode-canonical-property-names-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" - integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== + version "2.0.1" + resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz" + integrity sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg== unicode-emoji-modifier-base@^1.0.0: version "1.0.0" @@ -8423,20 +9410,20 @@ unicode-match-property-ecmascript@^2.0.0: unicode-canonical-property-names-ecmascript "^2.0.0" unicode-property-aliases-ecmascript "^2.0.0" -unicode-match-property-value-ecmascript@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz" - integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== +unicode-match-property-value-ecmascript@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz" + integrity sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg== unicode-property-aliases-ecmascript@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz" - integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== + version "2.2.0" + resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz" + integrity sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ== unified@^11.0.0, unified@^11.0.3, unified@^11.0.4: - version "11.0.4" - resolved "https://registry.npmjs.org/unified/-/unified-11.0.4.tgz" - integrity sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ== + version "11.0.5" + resolved "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz" + integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== dependencies: "@types/unist" "^3.0.0" bail "^2.0.0" @@ -8454,9 +9441,9 @@ unique-string@^3.0.0: crypto-random-string "^4.0.0" unist-util-is@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz" - integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw== + version "6.0.1" + resolved "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz" + integrity sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g== dependencies: "@types/unist" "^3.0.0" @@ -8474,14 +9461,6 @@ unist-util-position@^5.0.0: dependencies: "@types/unist" "^3.0.0" -unist-util-remove-position@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz" - integrity sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q== - dependencies: - "@types/unist" "^3.0.0" - unist-util-visit "^5.0.0" - unist-util-stringify-position@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz" @@ -8490,9 +9469,9 @@ unist-util-stringify-position@^4.0.0: "@types/unist" "^3.0.0" unist-util-visit-parents@^6.0.0: - version "6.0.1" - resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz" - integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw== + version "6.0.2" + resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz" + integrity sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ== dependencies: "@types/unist" "^3.0.0" unist-util-is "^6.0.0" @@ -8511,15 +9490,15 @@ universalify@^2.0.0: resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== -unpipe@~1.0.0, unpipe@1.0.0: +unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -update-browserslist-db@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz" - integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== +update-browserslist-db@^1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz" + integrity sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A== dependencies: escalade "^3.2.0" picocolors "^1.1.1" @@ -8561,9 +9540,9 @@ url-loader@^4.1.1: schema-utils "^3.0.0" use-sync-external-store@^1.4.0: - version "1.5.0" - resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz" - integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A== + version "1.6.0" + resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz" + integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" @@ -8585,6 +9564,11 @@ utils-merge@1.0.1: resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== +uuid@^11.1.0: + version "11.1.0" + resolved "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz" + integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A== + uuid@^8.3.2: version "8.3.2" resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" @@ -8601,30 +9585,64 @@ vary@~1.1.2: integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== vfile-location@^5.0.0: - version "5.0.2" - resolved "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.2.tgz" - integrity sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg== + version "5.0.3" + resolved "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz" + integrity sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg== dependencies: "@types/unist" "^3.0.0" vfile "^6.0.0" vfile-message@^4.0.0: - version "4.0.2" - resolved "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz" - integrity sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw== + version "4.0.3" + resolved "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz" + integrity sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw== dependencies: "@types/unist" "^3.0.0" unist-util-stringify-position "^4.0.0" vfile@^6.0.0, vfile@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz" - integrity sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw== + version "6.0.3" + resolved "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz" + integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== dependencies: "@types/unist" "^3.0.0" - unist-util-stringify-position "^4.0.0" vfile-message "^4.0.0" +vscode-jsonrpc@8.2.0: + version "8.2.0" + resolved "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz" + integrity sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA== + +vscode-languageserver-protocol@3.17.5: + version "3.17.5" + resolved "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz" + integrity sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg== + dependencies: + vscode-jsonrpc "8.2.0" + vscode-languageserver-types "3.17.5" + +vscode-languageserver-textdocument@~1.0.11: + version "1.0.12" + resolved "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz" + integrity sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA== + +vscode-languageserver-types@3.17.5: + version "3.17.5" + resolved "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz" + integrity sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg== + +vscode-languageserver@~9.0.1: + version "9.0.1" + resolved "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz" + integrity sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g== + dependencies: + vscode-languageserver-protocol "3.17.5" + +vscode-uri@~3.0.8: + version "3.0.8" + resolved "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz" + integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw== + watchpack@^2.4.1: version "2.4.2" resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz" @@ -8732,7 +9750,7 @@ webpack-sources@^3.2.3: resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -"webpack@^4.0.0 || ^5.0.0", webpack@^5.0.0, webpack@^5.1.0, webpack@^5.20.0, webpack@^5.88.1, webpack@^5.95.0, "webpack@>=4.41.1 || 5.x", webpack@>=5, "webpack@3 || 4 || 5": +webpack@^5.88.1, webpack@^5.95.0: version "5.96.1" resolved "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz" integrity sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA== @@ -8775,7 +9793,7 @@ webpackbar@^6.0.1: std-env "^3.7.0" wrap-ansi "^7.0.0" -websocket-driver@^0.7.4, websocket-driver@>=0.5.1: +websocket-driver@>=0.5.1, websocket-driver@^0.7.4: version "0.7.4" resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz" integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== @@ -8875,15 +9893,20 @@ yallist@^4.0.0: resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml@^2.8.1: + version "2.8.1" + resolved "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz" + integrity sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw== + yocto-queue@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz" - integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== + version "1.2.2" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz" + integrity sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ== -"zod@^3.25.76 || ^4", zod@^4.1.8: - version "4.1.11" - resolved "https://registry.npmjs.org/zod/-/zod-4.1.11.tgz" - integrity sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg== +zod@^4.1.8: + version "4.1.12" + resolved "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz" + integrity sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ== zwitch@^2.0.0: version "2.0.4" diff --git a/global.json b/global.json index 67b0c9d008..2139291fc8 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.100", + "version": "10.0.101", "rollForward": "latestMajor", "allowPrerelease": true }, diff --git a/tools/speed-comparison/UnifiedTests/SetupTeardownTests.cs b/tools/speed-comparison/UnifiedTests/SetupTeardownTests.cs new file mode 100644 index 0000000000..613d9a842a --- /dev/null +++ b/tools/speed-comparison/UnifiedTests/SetupTeardownTests.cs @@ -0,0 +1,380 @@ +using System.Text; +using System.Threading.Tasks; + +namespace UnifiedTests; + +[TestClass] +#if XUNIT3 +public class SetupTeardownTests : IDisposable +#else +public class SetupTeardownTests +#endif +{ + // Simulated expensive state + private byte[] _databaseConnection; + private List _tempFiles; + private HttpClient _httpClient; + private StringBuilder _logBuilder; + +#if TUNIT + [Before(Test)] + public async Task Setup() +#elif XUNIT3 + public SetupTeardownTests() +#elif NUNIT + [SetUp] + public async Task Setup() +#elif MSTEST + [TestInitialize] + public async Task Setup() +#else + public async Task Setup() +#endif + { +#if XUNIT3 + SetupCore().GetAwaiter().GetResult(); + } + + private async Task SetupCore() + { +#endif + // Simulate expensive database connection initialization + _databaseConnection = new byte[1024 * 100]; // 100KB allocation + for (int i = 0; i < _databaseConnection.Length; i++) + { + _databaseConnection[i] = (byte)(i % 256); + } + + // Simulate file system setup + _tempFiles = []; + await Task.Delay(5); // Simulate async I/O + + // Simulate HTTP client initialization + _httpClient = new HttpClient + { + Timeout = TimeSpan.FromSeconds(30) + }; + + // Simulate logging infrastructure + _logBuilder = new StringBuilder(1000); + _logBuilder.AppendLine($"Test started at {DateTime.UtcNow}"); + + // Simulate loading configuration + await Task.Delay(5); + } + +#if TUNIT + [After(Test)] + public async Task Cleanup() +#elif XUNIT3 + public void Dispose() +#elif NUNIT + [TearDown] + public async Task Cleanup() +#elif MSTEST + [TestCleanup] + public async Task Cleanup() +#else + public async Task Cleanup() +#endif + { +#if XUNIT3 + CleanupCore().GetAwaiter().GetResult(); + } + + private async Task CleanupCore() + { +#endif + // Simulate database connection cleanup + if (_databaseConnection != null) + { + Array.Clear(_databaseConnection, 0, _databaseConnection.Length); + _databaseConnection = null!; + } + + // Simulate file cleanup + if (_tempFiles != null) + { + await Task.Delay(5); // Simulate async I/O + _tempFiles.Clear(); + _tempFiles = null!; + } + + // Cleanup HTTP client + _httpClient?.Dispose(); + _httpClient = null!; + + // Finalize logging + if (_logBuilder != null) + { + _logBuilder.AppendLine($"Test completed at {DateTime.UtcNow}"); + _logBuilder.Clear(); + _logBuilder = null!; + } + + await Task.Delay(5); // Simulate async cleanup + } + + [Test] + public void DatabaseOperationTest() + { + // Simulate database query + var sum = 0; + for (int i = 0; i < 1000; i++) + { + sum += _databaseConnection[i]; + } + _logBuilder.AppendLine($"Database operation result: {sum}"); + } + + [Test] + public async Task AsyncDatabaseOperationTest() + { + // Simulate async database operation + await Task.Delay(10); + var sum = 0; + for (int i = 0; i < 1000; i++) + { + sum += _databaseConnection[i]; + } + _logBuilder.AppendLine($"Async database operation result: {sum}"); + } + + [Test] + public void FileSystemOperationTest() + { + // Simulate file operations + for (int i = 0; i < 10; i++) + { + _tempFiles.Add($"temp_file_{i}.txt"); + } + _logBuilder.AppendLine($"Created {_tempFiles.Count} temp files"); + } + + [Test] + public async Task AsyncFileSystemOperationTest() + { + // Simulate async file operations + await Task.Delay(10); + for (int i = 0; i < 10; i++) + { + _tempFiles.Add($"async_temp_file_{i}.txt"); + } + _logBuilder.AppendLine($"Created {_tempFiles.Count} temp files asynchronously"); + } + + [Test] + public void HttpClientOperationTest() + { + // Simulate HTTP client usage + var timeout = _httpClient.Timeout; + _logBuilder.AppendLine($"HTTP client timeout: {timeout.TotalSeconds}s"); + } + + [Test] + public async Task AsyncHttpClientOperationTest() + { + // Simulate async HTTP operation + await Task.Delay(10); + var timeout = _httpClient.Timeout; + _logBuilder.AppendLine($"Async HTTP client timeout: {timeout.TotalSeconds}s"); + } + + [Test] + public void LoggingOperationTest() + { + // Simulate logging + for (int i = 0; i < 50; i++) + { + _logBuilder.AppendLine($"Log entry {i}"); + } + } + + [Test] + public async Task AsyncLoggingOperationTest() + { + // Simulate async logging + await Task.Delay(10); + for (int i = 0; i < 50; i++) + { + _logBuilder.AppendLine($"Async log entry {i}"); + } + } + + [Test] + public void MemoryIntensiveOperationTest() + { + // Simulate memory-intensive operation + var tempBuffer = new byte[1024 * 50]; // 50KB + for (int i = 0; i < tempBuffer.Length; i++) + { + tempBuffer[i] = _databaseConnection[i % _databaseConnection.Length]; + } + var sum = tempBuffer.Sum(b => (int)b); + _logBuilder.AppendLine($"Memory operation result: {sum}"); + } + + [Test] + public async Task AsyncMemoryIntensiveOperationTest() + { + // Simulate async memory-intensive operation + await Task.Delay(10); + var tempBuffer = new byte[1024 * 50]; // 50KB + for (int i = 0; i < tempBuffer.Length; i++) + { + tempBuffer[i] = _databaseConnection[i % _databaseConnection.Length]; + } + var sum = tempBuffer.Sum(b => (int)b); + _logBuilder.AppendLine($"Async memory operation result: {sum}"); + } + + [Test] + public void ComputationTest() + { + // Simulate computation + var result = 0; + for (int i = 0; i < 10000; i++) + { + result += i * i; + } + _logBuilder.AppendLine($"Computation result: {result}"); + } + + [Test] + public async Task AsyncComputationTest() + { + // Simulate async computation + await Task.Delay(10); + var result = 0; + for (int i = 0; i < 10000; i++) + { + result += i * i; + } + _logBuilder.AppendLine($"Async computation result: {result}"); + } + + [Test] + public void StringManipulationTest() + { + // Simulate string operations + var sb = new StringBuilder(); + for (int i = 0; i < 100; i++) + { + sb.Append($"Item {i}, "); + } + _logBuilder.AppendLine($"String length: {sb.Length}"); + } + + [Test] + public async Task AsyncStringManipulationTest() + { + // Simulate async string operations + await Task.Delay(10); + var sb = new StringBuilder(); + for (int i = 0; i < 100; i++) + { + sb.Append($"Item {i}, "); + } + _logBuilder.AppendLine($"Async string length: {sb.Length}"); + } + + [Test] + public void CollectionOperationTest() + { + // Simulate collection operations + var numbers = Enumerable.Range(0, 1000).ToList(); + var filtered = numbers.Where(n => n % 2 == 0).ToList(); + _logBuilder.AppendLine($"Filtered count: {filtered.Count}"); + } + + [Test] + public async Task AsyncCollectionOperationTest() + { + // Simulate async collection operations + await Task.Delay(10); + var numbers = Enumerable.Range(0, 1000).ToList(); + var filtered = numbers.Where(n => n % 2 == 0).ToList(); + _logBuilder.AppendLine($"Async filtered count: {filtered.Count}"); + } + + [Test] + public void DateTimeOperationTest() + { + // Simulate datetime operations + var start = DateTime.UtcNow; + var timestamps = new List(); + for (int i = 0; i < 100; i++) + { + timestamps.Add(start.AddSeconds(i)); + } + _logBuilder.AppendLine($"Timestamps created: {timestamps.Count}"); + } + + [Test] + public async Task AsyncDateTimeOperationTest() + { + // Simulate async datetime operations + await Task.Delay(10); + var start = DateTime.UtcNow; + var timestamps = new List(); + for (int i = 0; i < 100; i++) + { + timestamps.Add(start.AddSeconds(i)); + } + _logBuilder.AppendLine($"Async timestamps created: {timestamps.Count}"); + } + + [Test] + public void DictionaryOperationTest() + { + // Simulate dictionary operations + var dict = new Dictionary(); + for (int i = 0; i < 100; i++) + { + dict[$"key_{i}"] = i * 2; + } + _logBuilder.AppendLine($"Dictionary size: {dict.Count}"); + } + + [Test] + public async Task AsyncDictionaryOperationTest() + { + // Simulate async dictionary operations + await Task.Delay(10); + var dict = new Dictionary(); + for (int i = 0; i < 100; i++) + { + dict[$"key_{i}"] = i * 2; + } + _logBuilder.AppendLine($"Async dictionary size: {dict.Count}"); + } + + [Test] + public void JsonOperationTest() + { + // Simulate JSON serialization + var data = new + { + Id = 123, + Name = "Test Data", + Values = Enumerable.Range(0, 50).ToArray() + }; + var json = System.Text.Json.JsonSerializer.Serialize(data); + _logBuilder.AppendLine($"JSON length: {json.Length}"); + } + + [Test] + public async Task AsyncJsonOperationTest() + { + // Simulate async JSON operations + await Task.Delay(10); + var data = new + { + Id = 123, + Name = "Test Data", + Values = Enumerable.Range(0, 50).ToArray() + }; + var json = System.Text.Json.JsonSerializer.Serialize(data); + _logBuilder.AppendLine($"Async JSON length: {json.Length}"); + } +} diff --git a/tools/tunit-nuget-tester/TUnit.NugetTester/TUnit.NugetTester/AotCompatibilityTests.cs b/tools/tunit-nuget-tester/TUnit.NugetTester/TUnit.NugetTester/AotCompatibilityTests.cs index 9702c05d5e..454de33ccf 100644 --- a/tools/tunit-nuget-tester/TUnit.NugetTester/TUnit.NugetTester/AotCompatibilityTests.cs +++ b/tools/tunit-nuget-tester/TUnit.NugetTester/TUnit.NugetTester/AotCompatibilityTests.cs @@ -1,3 +1,5 @@ +using TUnit.Assertions.Enums; + namespace TUnit.NugetTester; /// @@ -48,4 +50,32 @@ public async Task PropertyInjection_ShouldNotTriggerAotWarnings() await Assert.That(InjectedProperty).IsNotNull(); await Assert.That(InjectedProperty).IsEqualTo("test value"); } + + /// + /// Tests for issue #3851 - IsEquivalentTo with custom comparer should be AOT-compatible + /// When a custom comparer is provided, no reflection is used, so the method should not + /// have RequiresUnreferencedCode attribute and should be safe for AOT. + /// + [Test] + public async Task IsEquivalentTo_WithCustomComparer_ShouldNotTriggerAotWarnings() + { + // This test verifies that using IsEquivalentTo with a custom comparer doesn't trigger IL2026/IL3050 + // The custom comparer path doesn't use StructuralEqualityComparer which requires reflection + var list1 = new List { 1, 2, 3 }; + var list2 = new List { 3, 2, 1 }; + + // Using explicit comparer - should be AOT-safe + await Assert.That(list1).IsEquivalentTo(list2, EqualityComparer.Default); + } + + [Test] + public async Task IsEquivalentTo_WithCustomComparer_OrderMatching_ShouldNotTriggerAotWarnings() + { + // Verify that custom comparer works with both ordering modes + var list1 = new List { "a", "b", "c" }; + var list2 = new List { "a", "b", "c" }; + + // Using custom comparer with order matching - should be AOT-safe + await Assert.That(list1).IsEquivalentTo(list2, StringComparer.OrdinalIgnoreCase, CollectionOrdering.Matching); + } }