accordant-troubleshooting

$npx mdskill add microsoft/accordant/accordant-troubleshooting

This skill covers common mistakes, debugging techniques, and how to fix typical issues.

SKILL.md

.github/skills/accordant-troubleshootingView on GitHub ↗
---
name: accordant-troubleshooting
description: Common mistakes and debugging tips for Accordant specs - use this skill when tests fail unexpectedly or specs don't behave as expected
---

# Troubleshooting Accordant

This skill covers common mistakes, debugging techniques, and how to fix typical issues.

## Test Failures

### "No matching expected outcome"

The response doesn't match any `Expect.That()` predicate.

**Diagnosis:**
```csharp
// Check what the spec expects vs what you got
var (isValid, message, _) = spec.Allows(operation, request, actualResponse, stateProfile);
Console.WriteLine(message);  // Shows which expectation failed and why
```

**Common causes:**
1. **Predicate too strict**: Response has extra fields or slightly different values
2. **Wrong state**: The spec thinks it's in state A, but the system is in state B
3. **Missing error case**: You didn't handle an error condition in Apply

**Fix:**
```csharp
// Add explanation to predicates for better debugging
Expect.That<ApiResult<User>>(
    r => r.IsSuccess && r.Data.Name == expectedName,
    $"Expected success with name '{expectedName}', got status={r?.StatusCode}, name='{r?.Data?.Name}'")
```

### "State mismatch after operation"

Tracked state diverges from actual system state.

**Common causes:**
1. **Forgot to reset state**: Previous test left data behind
2. **Missing ThenState**: Operation modifies state but spec uses `.SameState()`
3. **Wrong state update**: ThenState modifies wrong fields

**Fix:**
```csharp
// Ensure BeforeEachAsync properly resets
var results = await spec.RunTests(context, initialState, testCases, new TestExecutionOptions
{
    BeforeEachAsync = async _ =>
    {
        // Actually verify reset worked
        await client.DeleteAll();
        var count = await client.GetCount();
        if (count != 0) throw new Exception("Reset failed!");
    }
});
```

### Tests Pass Individually, Fail in Sequence

**Common causes:**
1. **Shared mutable state** in test fixtures
2. **Incomplete reset** between tests
3. **Timing issues** with async operations

**Fix:**
```csharp
// Create fresh client for each test
var results = await spec.RunTests(context, initialState, testCases, new TestExecutionOptions
{
    BeforeEachAsync = async ctx =>
    {
        // Fresh client, fresh start
        var httpClient = _factory.CreateClient();
        ctx.Context.Register(new ApiClient(httpClient));
        await ResetDatabase();
    }
});
```

## State Issues

### Modifying Original State

```csharp
// ❌ WRONG - modifying original state
spec.Operation<string, ApiResult<decimal>>("Deposit", (accountId, state) =>
{
    state.Accounts[accountId] += 100;  // BAD! Modifies original
    return Expect.That(...).SameState();  // State already corrupted
});

// ✅ CORRECT - modify the clone in ThenState
spec.Operation<string, ApiResult<decimal>>("Deposit", (accountId, state) =>
{
    var newBalance = state.Accounts[accountId] + 100;
    return Expect.That<ApiResult<decimal>>(r => r.Data == newBalance)
           .ThenState<BankState>(nextState => nextState.Accounts[accountId] = newBalance);
});
```

### State Class Missing [State] Attribute

```csharp
// ❌ Won't work - no cloning/equality support
public class BankState
{
    public Dictionary<string, decimal> Accounts { get; set; }
}

// ✅ Add [State] attribute
[State]
public partial class BankState
{
    public Dictionary<string, decimal> Accounts { get; set; } = new();
}
```

### Nested State Not Marked

```csharp
// ❌ UserState isn't cloned properly
[State]
public partial class AppState
{
    public Dictionary<string, UserState> Users { get; set; }  // UserState needs [State] too!
}

public class UserState { ... }  // Missing [State]

// ✅ Mark all nested state classes
[State]
public partial class AppState
{
    public Dictionary<string, UserState> Users { get; set; } = new();
}

[State]
public partial class UserState
{
    public string Name { get; set; } = string.Empty;
}
```

## Execution Issues

### Operations Not Bound

```
System.InvalidOperationException: No execution binding found for operation 'CreateAccount'
```

**Fix:**
```csharp
spec.ExecuteWith<ApiClient>()
    .Bind<string, ApiResult<decimal>>("CreateAccount",  // Must match operation name exactly
        (client, accountId) => client.CreateAccount(accountId).Result);
```

### Type Mismatch in Binding

```csharp
// ❌ Wrong - request type doesn't match
spec.Operation<(string, decimal), ApiResult<decimal>>("Withdraw", ...);  // Tuple
spec.ExecuteWith<ApiClient>()
    .Bind<string, ApiResult<decimal>>("Withdraw", ...);  // String - MISMATCH!

// ✅ Types must match exactly
spec.ExecuteWith<ApiClient>()
    .Bind<(string, decimal), ApiResult<decimal>>("Withdraw",
        (client, req) => client.Withdraw(req.Item1, req.Item2).Result);
```

### Async/Await Issues

```csharp
// ❌ Potential deadlock with .Result
spec.ExecuteWith<ApiClient>()
    .Bind<string, ApiResult<User>>("GetUser",
        (client, id) => client.GetUserAsync(id).Result);  // Can deadlock!

// ✅ Use async binding
spec.ExecuteWith<ApiClient>()
    .BindAsync<string, ApiResult<User>>("GetUser",
        async (client, id) => await client.GetUserAsync(id));
```

## Test Generation Issues

### Too Many Test Cases

**Diagnosis:** Generation produces thousands of tests

**Fixes:**
```csharp
var options = new TestGenerationOptions
{
    // Reduce depth
    MaxDepth = 3,  // Instead of 5
    
    // Add state constraint
    StateConstraint = state =>
    {
        var s = (AppState)state;
        return s.Users.Count <= 2 && 
               s.Users.Values.Sum(u => u.Todos.Count) <= 4;
    }
};
```

### No Test Cases Generated

**Diagnosis:** `testCases.Count == 0`

**Common causes:**
1. **Initial state doesn't allow any operation**
2. **StateConstraint rejects initial state**
3. **Empty InputSet**

**Fix:**
```csharp
// Verify initial state is valid
var initialState = new AppState();
Assert.True(options.StateConstraint?.Invoke(initialState) ?? true, "Initial state rejected!");

// Ensure inputs aren't empty
Assert.That(inputs.Count, Is.GreaterThan(0));
```

### State Space Explosion

**Symptoms:** Generation hangs or runs out of memory

**Fix:**
```csharp
var options = new TestGenerationOptions
{
    MaxDepth = 4,
    StateConstraint = state =>
    {
        var s = (AppState)state;
        // Aggressive pruning
        return s.Items.Count <= 3;
    },
    // Or use random sampling
    SequentialTestCaseAlgorithm = SequentialTestCaseAlgorithms.CreateRandomWalk(
        numberOfWalks: 100,
        maxWalkLength: 5,
        seed: 42)
};
```

## Concurrency Test Issues

### All Concurrent Tests Fail

**Common cause:** Implementation has no concurrency control — race conditions everywhere

**Diagnosis:** Check if sequential tests pass first
```csharp
// Run sequential tests first
var seqResults = await spec.RunTests(context, initialState, testCases);
Assert.That(seqResults.All(r => r.Success), "Fix sequential tests first!");

// Then run concurrent
var concTestCases = spec.GenerateConcurrentTests(initialState, inputs, options);
var concResults = await spec.RunTests(context, initialState, concTestCases);
```

### Non-Deterministic Failures

**Symptoms:** Same test sometimes passes, sometimes fails

**Common causes:**
1. **Race conditions** (the bug you're looking for!)
2. **Incomplete state reset**
3. **External system interference**

**Diagnosis:**
```csharp
// Run same test multiple times
for (int i = 0; i < 10; i++)
{
    await ResetState();
    var concTestCases = spec.GenerateConcurrentTests(initialState, inputs, options);
    var result = await spec.RunTests(context, initialState, concTestCases);
    Console.WriteLine($"Run {i}: {(result.All(r => r.Success) ? "PASS" : "FAIL")}");
}
```

## Async Operation Issues

### Polling Never Terminates

**Symptoms:** Test hangs on async operation

**Diagnosis:**
```csharp
// Check isTerminal predicate
var state = GetCurrentState();
var isTerminal = stepFunction.IsTerminal(state);
Console.WriteLine($"IsTerminal: {isTerminal}, State: {state}");
```

**Common causes:**
1. **isTerminal predicate is wrong** — never returns true
2. **Background work never completes** — liveness bug
3. **Wrong state being checked**

**Fix:**
```csharp
.Triggers(AsyncOperation.Create<JobQueueState>(
    // Make sure this actually becomes true
    isTerminal: s => 
    {
        var status = s.Jobs.GetValueOrDefault(jobId)?.Status;
        Console.WriteLine($"Checking terminal: status={status}");
        return status != JobStatus.Pending;
    },
    ...
))
```

### State Profile Has Too Many States

**Symptoms:** After async operation, many possible states tracked

**This is expected!** Non-determinism means multiple outcomes are valid. Observations narrow it down.

```csharp
// Observe to narrow down
var response = await client.GetJob(jobId);
(_, _, stateProfile) = spec.Allows(getJobOp, jobId, response, stateProfile);

// State count should decrease
Console.WriteLine($"Remaining possible states: {stateProfile.StatesAndStepFunctions.Count}");
```

## Debugging Tips

### Enable Detailed Logging

```csharp
var results = await spec.RunTests(context, initialState, testCases, new TestExecutionOptions
{
    OnStepExecuted = info =>
    {
        foreach (var (op, req, resp) in info.Operations)
            Console.WriteLine($"{op.Name}({req}) → {resp}");
    }
});
```

### Visualize the State Graph

```csharp
var dot = spec.VisualizeStateSpace(initialState, inputs, options);
File.WriteAllText("debug-graph.dot", dot);
// Run: dot -Tpng debug-graph.dot -o debug-graph.png
```

### Inspect a Single Test Case

```csharp
var failedCase = testCases.First(tc => !results[tc].Success);
Console.WriteLine($"Failed sequence:");
foreach (var step in failedCase.Steps)
{
    Console.WriteLine($"  {step.OperationName}({step.Request})");
}
```

### Manually Replay a Sequence

```csharp
var stateProfile = new StateProfile(initialState);

foreach (var step in failedCase.Steps)
{
    Console.WriteLine($"\n=== {step.OperationName} ===");
    Console.WriteLine($"Request: {step.Request}");
    Console.WriteLine($"Current states: {stateProfile.StatesAndStepFunctions.Count}");
    
    var response = await ExecuteStep(step);
    Console.WriteLine($"Response: {response}");
    
    var (isValid, message, newProfile) = spec.Allows(step.Operation, step.Request, response, stateProfile);
    Console.WriteLine($"Valid: {isValid}");
    if (!isValid) Console.WriteLine($"Error: {message}");
    
    stateProfile = newProfile;
}
```

## Getting Help

1. **Check the samples**: `Samples/` folder has working examples
2. **Read the concepts docs**: `docs/concepts/` explains the theory
3. **Visualize**: State graphs often reveal the problem
4. **Simplify**: Reduce to minimal failing case

More from microsoft/accordant

SkillDescription
accordant-async-operationsHow to model background work, step functions, and polling - use this skill when testing async workflows, job queues, or operations that complete in the background
accordant-concurrencyHow to test for race conditions using concurrent tests and linearizability - use this skill when testing concurrent operations or finding race condition bugs
accordant-cross-languageTesting systems not written in .NET - trace-based conformance testing, JSON test plan export, and executing from any language. Use when the user's system is in Python, Go, Java, Rust, C++, or any non-.NET language, or when they ask about trace validation or exporting test cases.
accordant-operationsHow to define operations with Apply and Execute methods - use this skill when creating spec operations or understanding the Apply/Execute pattern
accordant-overviewWhat Accordant is, when to use model-based testing, and core concepts - use this skill when starting with Accordant or explaining its purpose
accordant-patternsCommon patterns in Accordant - response-dependent state, request derivations, error handling, and HTTP integration
accordant-quickrefQuick reference and cheatsheet for Accordant API - use this skill for quick syntax lookup or code snippets
accordant-stateHow to design state models in Accordant - use this skill when defining what your system tracks between operations
accordant-test-generationHow Accordant generates and runs tests - use this skill when configuring test generation, understanding state graphs, or running tests