Skip to content

Add type provider integration tests for CancellationToken-overloaded methods#338

Merged
sergey-tihon merged 3 commits intorepo-assist/feat-cancellationtoken-issue-212-2026-03-23-448c1cde90ec7d95from
copilot/sub-pr-336
Mar 23, 2026
Merged

Add type provider integration tests for CancellationToken-overloaded methods#338
sergey-tihon merged 3 commits intorepo-assist/feat-cancellationtoken-issue-212-2026-03-23-448c1cde90ec7d95from
copilot/sub-pr-336

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 23, 2026

The CancellationToken PR lacked integration tests that exercise the TP-generated CT overloads end-to-end against a real HTTP server.

Changes

  • v3/Swashbuckle.CancellationToken.Tests.fs — new test module with 4 [<Fact>] tests calling TP-generated methods with explicit CT arguments:

    • CancellationToken.None on GET and POST operations
    • Active CancellationTokenSource token (happy path)
    • Pre-cancelled token → asserts OperationCanceledException
  • SwaggerProvider.ProviderTests.fsproj — includes the new file, ordered after Swashbuckle.ReturnControllers.Tests.fs (which defines the api instance these tests reuse)

// Explicit CancellationToken.None
let! result = api.GetApiReturnBoolean(CancellationToken.None)

// Active token
use cts = new CancellationTokenSource()
let! result = api.GetApiReturnInt32(cts.Token)

// Pre-cancelled → OperationCanceledException
use cts = new CancellationTokenSource()
cts.Cancel()
let! _ = api.GetApiReturnString(cts.Token)  // throws

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 23, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • petstore.swagger.io
    • Triggering command: /usr/share/dotnet/dotnet /usr/share/dotnet/dotnet /usr/share/dotnet/sdk/10.0.102/FSharp/fsc.dll @/tmp/MSBuildTempiXPNHK/tmpa0d7dc1fc7d0470caabd2d412b9e0d9f.rsp git conf�� --local credential.helper (dns block)
    • Triggering command: /usr/share/dotnet/dotnet /usr/share/dotnet/dotnet /usr/share/dotnet/sdk/10.0.102/FSharp/fsc.dll @/tmp/MSBuildTempRgGS22/tmp88efa443e8d448f99de291eeff3ced7b.rsp git ogs.�� linuxagent-logcollector.slice (dns block)
    • Triggering command: /usr/share/dotnet/dotnet /usr/share/dotnet/dotnet /usr/share/dotnet/sdk/10.0.102/FSharp/fsc.dll @/tmp/MSBuildTempL58rKC/tmp5f8b101f8b714b81aff5ebcb17be25ae.rsp bash --no�� --noprofile (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title [WIP] Add CancellationToken support to OpenApiClientProvider generated methods Add type provider integration tests for CancellationToken-overloaded methods Mar 23, 2026
Copilot AI requested a review from sergey-tihon March 23, 2026 20:47
@sergey-tihon
Copy link
Copy Markdown
Member

@copilot ci still failing

… invalid IL for structs

Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/cd02aac9-3f51-40c9-af2f-11d1eda31d77
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 23, 2026

@copilot ci still failing

Fixed in e74b3e2. Root cause: Expr.Coerce(expr, typeof<CancellationToken>) generates a castclass IL instruction which is only valid for reference types — CancellationToken is a struct, so the JIT rejected it with InvalidProgramException.

Fix: extract the CT argument by position (always the last parameter in the overload) and use Expr.Cast<Threading.CancellationToken>(ctArg) which is a typed wrapper with no IL cast instruction. Also replaced the fragile sVar.Name = "cancellationToken" check that could collide with OpenAPI spec parameters of the same name.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • petstore.swagger.io
    • Triggering command: /usr/share/dotnet/dotnet /usr/share/dotnet/dotnet /usr/share/dotnet/sdk/10.0.102/FSharp/fsc.dll @/tmp/MSBuildTemppJyfcF/tmpf860966a85c0444d9d34ff83f7d6577f.rsp (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@sergey-tihon sergey-tihon marked this pull request as ready for review March 23, 2026 21:27
Copilot AI review requested due to automatic review settings March 23, 2026 21:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds end-to-end integration coverage for type-provider-generated CancellationToken overloads against the existing Swashbuckle test server, and updates the v3 operation invocation compiler to reliably extract the CT argument.

Changes:

  • Add new v3 integration tests that call TP-generated methods with explicit CancellationToken arguments (including pre-cancelled token behavior).
  • Register the new test module in the provider test project with correct compile ordering.
  • Adjust v3 OperationCompiler to extract CancellationToken by argument position (last argument) instead of by name.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
tests/SwaggerProvider.ProviderTests/v3/Swashbuckle.CancellationToken.Tests.fs New integration tests exercising CT overloads (GET/POST, active token, pre-cancelled token).
tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj Includes the new test file after Swashbuckle.ReturnControllers.Tests.fs to reuse api.
src/SwaggerProvider.DesignTime/v3/OperationCompiler.fs Changes CT extraction in provided method invoke code to be position-based and avoid prior name/cast issues.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +276 to +287
// When the CancellationToken overload is generated, CancellationToken is always appended last.
// Extract it by position to avoid name-collision issues and invalid Expr.Coerce
// on a struct type (which generates an invalid castclass IL instruction).
let apiArgs, ct =
let allArgs = List.tail args // skip `this`

if includeCancellationToken then
match List.rev allArgs with
| ctArg :: revApiArgs -> List.rev revApiArgs, Expr.Cast<Threading.CancellationToken>(ctArg)
| [] -> failwith "Expected CancellationToken argument but argument list was empty"
else
allArgs, <@ Threading.CancellationToken.None @>
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR title/description focuses on adding integration tests, but this hunk also changes type provider code generation behavior (CancellationToken argument extraction). Please update the PR description/title to reflect the compiler change (or split into a separate PR) so reviewers don’t miss the functional impact.

Copilot uses AI. Check for mistakes.
if includeCancellationToken then
match List.rev allArgs with
| ctArg :: revApiArgs -> List.rev revApiArgs, Expr.Cast<Threading.CancellationToken>(ctArg)
| [] -> failwith "Expected CancellationToken argument but argument list was empty"
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This failwith message is likely only hit when TP invocation arguments are unexpected, but it would be much easier to diagnose if it included the operation/method name (e.g., providedMethodName) and the received argument count. Consider switching to failwithf with that context.

Suggested change
| [] -> failwith "Expected CancellationToken argument but argument list was empty"
| [] ->
failwithf
"Method '%s' expected a CancellationToken argument as the last parameter but received %d argument(s)."
providedMethodName
allArgs.Length

Copilot uses AI. Check for mistakes.
@sergey-tihon sergey-tihon merged commit b2f0d80 into repo-assist/feat-cancellationtoken-issue-212-2026-03-23-448c1cde90ec7d95 Mar 23, 2026
18 checks passed
@sergey-tihon sergey-tihon deleted the copilot/sub-pr-336 branch March 23, 2026 22:12
sergey-tihon added a commit that referenced this pull request Mar 25, 2026
…vider generated methods (closes #212) (#336)

* feat: add CancellationToken support to OpenApiClientProvider generated methods (closes #212)

- Add CallAsync overload with CancellationToken to ProvidedApiClientBase
- Thread CancellationToken from generated methods through to HttpClient.SendAsync
- Each generated method gains an optional cancellationToken parameter (defaults to CancellationToken.None)
- Backward-compatible: existing call sites without CT continue to work unchanged
- Add unit tests: success with CancellationToken.None, cancellation propagation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: trigger checks

* fix: replace optional struct CancellationToken parameter with method overloads (#337)

* Initial plan

* Fix: revert global.json and address CancellationToken build failures

Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/1861c3cb-6a0a-438a-aa31-f65b8c809f88

* fix: use method overloading for CancellationToken support instead of optional struct parameter

Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/1861c3cb-6a0a-438a-aa31-f65b8c809f88

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>

* Add type provider integration tests for CancellationToken-overloaded methods (#338)

* Fix: CT parameter name uniqueness in CancellationToken overload generation (#339)

* Initial plan

* fix: generate unique CT parameter name to avoid collision with OpenAPI params named 'cancellationToken'

Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/7d588ec7-c4df-4a6c-89f8-9c13c2472d29

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>

* Fix CancellationToken parameter ordering and name collision in v3 OperationCompiler (#341)

* Initial plan

* fix: insert CT between required and optional params; generate unique CT name

Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/b0c519de-0186-40ca-8174-42ed67a5316a

* fix: add explicit restore + --no-restore to BuildTests to fix NETSDK1005

Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/565d6633-576d-4587-b924-a29b0ea53c2c

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>

* refactor: use UniqueNameGenerator for CT param name uniqueness

Replace hand-coded recursive findUniqueName function with the existing
UniqueNameGenerator utility (already used in DefinitionCompiler and
for method name deduplication in OperationCompiler).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add optional occupiedNames parameter to UniqueNameGenerator constructor

Allows callers to pre-seed the generator with names that are already taken,
so MakeUnique will never return any of those names without a numeric suffix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor: single CallAsync overload + single generated method with optional CancellationToken

- Remove no-CT CallAsync overload from ProvidedApiClientBase; keep only the
  version with explicit CancellationToken (quotation code always supplies it)
- Remove double-compilation in OperationCompiler: one method per operation with
  optional cancellationToken (null default = default(CancellationToken).None)
- Update RuntimeHelpersTests to pass CancellationToken.None explicitly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* cleanup: simplify OperationCompiler and add default/async CT tests

- Use List.map instead of List.collect since compileOperation returns a single method
- Clean up comments in OperationCompiler
- Add test for calling generated method without CancellationToken (default token)
- Add test for async (PreferAsync=true) generated method without CancellationToken

* feat: propagate CancellationToken through ReadAsStringAsync/ReadAsStreamAsync via RuntimeHelpers

Add readContentAsString and readContentAsStream wrappers to RuntimeHelpers
with #if NET5_0_OR_GREATER guards, enabling CancellationToken propagation
in generated quotation code that must compile against netstandard2.0.
Also add explicit CancellationToken integration tests and conditional
CT support in ProvidedApiClientBase error path.

* test: add CT coverage for stream, text/plain, async cancellation, and async POST paths

* fix: async tests

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Sergey Tihon <sergey.tihon@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants