FuzzExtractExpressions fuzz-tests the full ExtractExpressions pipeline against arbitrary markdown strings. It validates that the function: 1. Never panics or returns an error for arbitrary input. 2. Returns only ExpressionMapping values with non-empty Original, EnvVar, and Content. 3. Every EnvVar h
(f *testing.F)
| 221 | // 5. Mappings for compound expressions (containing ||/&&) are accompanied by |
| 222 | // sub-expression mappings for steps.*/inputs.*/needs.* leaf tokens. |
| 223 | func FuzzExtractExpressions(f *testing.F) { |
| 224 | // Plain markdown (no expressions) |
| 225 | f.Add("This is plain text") |
| 226 | f.Add("") |
| 227 | f.Add("# Heading\n\nSome content.") |
| 228 | |
| 229 | // Single simple expressions |
| 230 | f.Add("Repo: ${{ github.repository }}") |
| 231 | f.Add("Actor: ${{ github.actor }}") |
| 232 | f.Add("Step output: ${{ steps.sanitized.outputs.text }}") |
| 233 | f.Add("Input: ${{ inputs.command }}") |
| 234 | |
| 235 | // Compound expressions |
| 236 | f.Add("Data: ${{ steps.sanitized.outputs.text || inputs.command }}") |
| 237 | f.Add("Data: ${{ needs.build.outputs.version && inputs.override }}") |
| 238 | |
| 239 | // Parenthesised compound expressions |
| 240 | f.Add("Data: ${{ (steps.a.outputs.x || inputs.y) && inputs.z }}") |
| 241 | f.Add("Data: ${{ steps.a.outputs.x || (inputs.y && inputs.z) }}") |
| 242 | f.Add("Data: ${{ (steps.a.outputs.x || inputs.y) && (steps.b.outputs.z || inputs.w) }}") |
| 243 | |
| 244 | // Multiple expressions in one markdown |
| 245 | f.Add("Repo: ${{ github.repository }}, Actor: ${{ github.actor }}") |
| 246 | f.Add("${{ steps.a.outputs.x || inputs.y }}, ${{ inputs.z }}") |
| 247 | |
| 248 | // Deprecated activation output syntax |
| 249 | f.Add("Content: ${{ needs.activation.outputs.text }}") |
| 250 | f.Add("Fallback: ${{ needs.activation.outputs.text || 'default' }}") |
| 251 | |
| 252 | // Malformed expressions |
| 253 | f.Add("Bad: ${{ }}") |
| 254 | f.Add("Unterminated: ${{ steps.a.outputs.x") |
| 255 | f.Add("No open: steps.a.outputs.x }}") |
| 256 | |
| 257 | // Expressions with special characters |
| 258 | f.Add("${{ github.event.inputs.name || 'default-name' }}") |
| 259 | f.Add("${{ inputs.repo || 'owner/repo' }}") |
| 260 | |
| 261 | f.Fuzz(func(t *testing.T, markdown string) { |
| 262 | extractor := NewExpressionExtractor() |
| 263 | |
| 264 | // Must never panic or return an error. |
| 265 | mappings, err := extractor.ExtractExpressions(markdown) |
| 266 | if err != nil { |
| 267 | t.Errorf("ExtractExpressions(%q) returned unexpected error: %v", markdown, err) |
| 268 | return |
| 269 | } |
| 270 | |
| 271 | // Every mapping must have non-empty fields and a valid env var. |
| 272 | envVarSeen := make(map[string]struct{}, len(mappings)) |
| 273 | for _, m := range mappings { |
| 274 | if m.Original == "" { |
| 275 | t.Errorf("ExtractExpressions(%q) returned mapping with empty Original", markdown) |
| 276 | } |
| 277 | if m.EnvVar == "" { |
| 278 | t.Errorf("ExtractExpressions(%q) returned mapping with empty EnvVar", markdown) |
| 279 | } |
| 280 | if m.Content == "" { |
nothing calls this directly
no test coverage detected