FuzzRenderExpressions fuzz-tests the combined extract-and-render pipeline against arbitrary markdown strings. It validates that the function: 1. Never panics or returns an error for arbitrary input. 2. After rendering, every ${{ ... }} expression that was extracted is replaced in the rendered output
(f *testing.F)
| 105 | // the GitHub Actions runner does at runtime) restores the mapping Content values |
| 106 | // into the text so that no __GH_AW_…__ placeholder remains. |
| 107 | func FuzzRenderExpressions(f *testing.F) { |
| 108 | // Plain markdown (no expressions) |
| 109 | f.Add("This is plain text") |
| 110 | f.Add("") |
| 111 | f.Add("# Heading\n\nSome content.") |
| 112 | |
| 113 | // Single simple expressions |
| 114 | f.Add("Repo: ${{ github.repository }}") |
| 115 | f.Add("Step output: ${{ steps.sanitized.outputs.text }}") |
| 116 | f.Add("Input: ${{ inputs.command }}") |
| 117 | |
| 118 | // Compound expressions |
| 119 | f.Add("Data: ${{ steps.sanitized.outputs.text || inputs.command }}") |
| 120 | f.Add("Data: ${{ needs.build.outputs.version && inputs.override }}") |
| 121 | |
| 122 | // Parenthesised compound expressions |
| 123 | f.Add("Data: ${{ (steps.a.outputs.x || inputs.y) && inputs.z }}") |
| 124 | f.Add("Data: ${{ (steps.a.outputs.x || inputs.y) && (steps.b.outputs.z || inputs.w) }}") |
| 125 | |
| 126 | // Multiple expressions in one markdown |
| 127 | f.Add("Repo: ${{ github.repository }}, Actor: ${{ github.actor }}") |
| 128 | f.Add("${{ steps.a.outputs.x || inputs.y }}, ${{ inputs.z }}") |
| 129 | |
| 130 | // Deprecated activation output syntax |
| 131 | f.Add("Content: ${{ needs.activation.outputs.text }}") |
| 132 | f.Add("Fallback: ${{ needs.activation.outputs.text || 'default' }}") |
| 133 | |
| 134 | // Malformed expressions |
| 135 | f.Add("Bad: ${{ }}") |
| 136 | f.Add("Unterminated: ${{ steps.a.outputs.x") |
| 137 | |
| 138 | // Expressions with string literals |
| 139 | f.Add("${{ inputs.repo || 'owner/repo' }}") |
| 140 | |
| 141 | f.Fuzz(func(t *testing.T, markdown string) { |
| 142 | extractor := NewExpressionExtractor() |
| 143 | |
| 144 | // Must never panic or return an error. |
| 145 | mappings, err := extractor.ExtractExpressions(markdown) |
| 146 | if err != nil { |
| 147 | t.Errorf("ExtractExpressions(%q) returned unexpected error: %v", markdown, err) |
| 148 | return |
| 149 | } |
| 150 | |
| 151 | rendered := extractor.ReplaceExpressionsWithEnvVars(markdown) |
| 152 | |
| 153 | // Build a lookup: Original -> mapping. |
| 154 | byOriginal := make(map[string]*ExpressionMapping, len(mappings)) |
| 155 | byEnvVar := make(map[string]*ExpressionMapping, len(mappings)) |
| 156 | for _, m := range mappings { |
| 157 | byOriginal[m.Original] = m |
| 158 | byEnvVar[m.EnvVar] = m |
| 159 | } |
| 160 | |
| 161 | // Each mapping's Original must no longer appear verbatim in the rendered output |
| 162 | // (it was replaced by the __EnvVar__ placeholder). |
| 163 | for _, m := range mappings { |
| 164 | if strings.Contains(rendered, m.Original) { |
nothing calls this directly
no test coverage detected