Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions internal/report/report_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package report

import (
"bytes"
"encoding/json"
"strings"
"testing"
Expand Down Expand Up @@ -98,6 +99,44 @@ func TestSARIF_DedupesRulesAcrossTargets(t *testing.T) {
}
}

func TestSARIF_RulesOrderIsDeterministic(t *testing.T) {
// the same input must serialize to byte-identical sarif every run; the rules
// list is built from a set, so its order has to be sorted rather than left to
// map iteration order.
results := []Result{
{Target: "https://t", Module: "aaa", Data: json.RawMessage(`{}`)},
{Target: "https://t", Module: "bbb", Data: json.RawMessage(`{}`)},
{Target: "https://t", Module: "ccc", Data: json.RawMessage(`{}`)},
{Target: "https://t", Module: "ddd", Data: json.RawMessage(`{}`)},
{Target: "https://t", Module: "eee", Data: json.RawMessage(`{}`)},
}
first, err := SARIF(results)
if err != nil {
t.Fatalf("SARIF: %v", err)
}
for i := 0; i < 200; i++ {
out, err := SARIF(results)
if err != nil {
t.Fatalf("SARIF: %v", err)
}
if !bytes.Equal(out, first) {
t.Fatalf("sarif output not reproducible across runs:\nfirst:\n%s\ngot:\n%s", first, out)
}
}

// and the ids come out in sorted order.
var doc sarifLog
if err := json.Unmarshal(first, &doc); err != nil {
t.Fatalf("invalid json: %v", err)
}
rules := doc.Runs[0].Tool.Driver.Rules
for i := 1; i < len(rules); i++ {
if rules[i-1].ID > rules[i].ID {
t.Errorf("rules not sorted: %q before %q", rules[i-1].ID, rules[i].ID)
}
}
}

func TestSARIF_Empty(t *testing.T) {
out, err := SARIF(nil)
if err != nil {
Expand Down
13 changes: 10 additions & 3 deletions internal/report/sarif.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package report
import (
"encoding/json"
"fmt"
"sort"
)

// sarif format/version constants pinned to the 2.1.0 schema so the output is
Expand Down Expand Up @@ -101,10 +102,16 @@ func SARIF(results []Result) ([]byte, error) {
}

// rules must list each id exactly once; build it from the set so duplicate
// modules across targets don't duplicate the rule.
rules := make([]sarifRule, 0, len(ruleSet))
// modules across targets don't duplicate the rule. sort the ids so the same
// input always yields byte-identical output instead of map iteration order.
ruleIDs := make([]string, 0, len(ruleSet))
for id := range ruleSet {
rules = append(rules, sarifRule{ID: id})
ruleIDs = append(ruleIDs, id)
}
sort.Strings(ruleIDs)
rules := make([]sarifRule, 0, len(ruleIDs))
for i := 0; i < len(ruleIDs); i++ {
rules = append(rules, sarifRule{ID: ruleIDs[i]})
}

doc := sarifLog{
Expand Down
Loading