@pi-in-go/pigpen-pi-typesafe 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CREDITS.md +14 -0
- package/LICENSE +22 -0
- package/README.md +45 -0
- package/extensions/pi-typesafe/branches_test.go +185 -0
- package/extensions/pi-typesafe/command.go +319 -0
- package/extensions/pi-typesafe/export_test.go +9 -0
- package/extensions/pi-typesafe/extension.go +188 -0
- package/extensions/pi-typesafe/extension_test.go +321 -0
- package/extensions/pi-typesafe/fakehost_test.go +548 -0
- package/extensions/pi-typesafe/format.go +191 -0
- package/extensions/pi-typesafe/format_test.go +75 -0
- package/extensions/pi-typesafe/go.mod +10 -0
- package/extensions/pi-typesafe/go.sum +2 -0
- package/extensions/pi-typesafe/go.work +11 -0
- package/extensions/pi-typesafe/harness_test.go +200 -0
- package/extensions/pi-typesafe/ownmodel_test.go +100 -0
- package/extensions/pi-typesafe/review_test.go +134 -0
- package/extensions/pi-typesafe/tool.go +193 -0
- package/extensions/pi-typesafe/twin_test.go +28 -0
- package/libs/pi-typesafe-api/CREDITS.md +14 -0
- package/libs/pi-typesafe-api/LICENSE +22 -0
- package/libs/pi-typesafe-api/README.md +30 -0
- package/libs/pi-typesafe-api/ask.go +62 -0
- package/libs/pi-typesafe-api/ask_test.go +76 -0
- package/libs/pi-typesafe-api/auth.go +249 -0
- package/libs/pi-typesafe-api/auth_test.go +131 -0
- package/libs/pi-typesafe-api/backends.go +336 -0
- package/libs/pi-typesafe-api/backends_test.go +404 -0
- package/libs/pi-typesafe-api/batch.go +202 -0
- package/libs/pi-typesafe-api/batch_test.go +202 -0
- package/libs/pi-typesafe-api/battery_test.go +41 -0
- package/libs/pi-typesafe-api/calibrate.go +354 -0
- package/libs/pi-typesafe-api/calibrate_test.go +186 -0
- package/libs/pi-typesafe-api/client.go +615 -0
- package/libs/pi-typesafe-api/client_test.go +490 -0
- package/libs/pi-typesafe-api/credentials.go +252 -0
- package/libs/pi-typesafe-api/credentials_test.go +216 -0
- package/libs/pi-typesafe-api/doc.go +14 -0
- package/libs/pi-typesafe-api/errors.go +143 -0
- package/libs/pi-typesafe-api/evaluation.go +86 -0
- package/libs/pi-typesafe-api/evaluation_schema.json +264 -0
- package/libs/pi-typesafe-api/gaps_test.go +77 -0
- package/libs/pi-typesafe-api/go.mod +9 -0
- package/libs/pi-typesafe-api/go.sum +2 -0
- package/libs/pi-typesafe-api/helpers_test.go +169 -0
- package/libs/pi-typesafe-api/hostmodel/hostmodel.go +87 -0
- package/libs/pi-typesafe-api/json.go +299 -0
- package/libs/pi-typesafe-api/json_test.go +92 -0
- package/libs/pi-typesafe-api/ownmodel_test.go +79 -0
- package/libs/pi-typesafe-api/package.json +40 -0
- package/libs/pi-typesafe-api/provenance.json +18 -0
- package/libs/pi-typesafe-api/review_test.go +23 -0
- package/libs/pi-typesafe-api/schema.go +473 -0
- package/libs/pi-typesafe-api/schema_test.go +262 -0
- package/libs/pi-typesafe-api/testdata/tools/typebox-messages.mts +5 -0
- package/libs/pi-typesafe-api/testdata/typebox-messages.json +285 -0
- package/libs/pi-typesafe-api/twin_test.go +28 -0
- package/libs/pi-typesafe-api/ui/fakehost_test.go +548 -0
- package/libs/pi-typesafe-api/ui/keyprompt.go +115 -0
- package/libs/pi-typesafe-api/ui/login.go +106 -0
- package/libs/pi-typesafe-api/ui/twin_test.go +28 -0
- package/libs/pi-typesafe-api/ui/ui_test.go +285 -0
- package/libs/pi-typesafe-api/usage.go +366 -0
- package/libs/pi-typesafe-api/usage_test.go +139 -0
- package/libs/typesafe/CONTRACT.md +125 -0
- package/libs/typesafe/CREDITS.md +37 -0
- package/libs/typesafe/LICENSE +23 -0
- package/libs/typesafe/README.md +19 -0
- package/libs/typesafe/go.mod +3 -0
- package/libs/typesafe/libraries/ownmodel/backend_test.go +496 -0
- package/libs/typesafe/libraries/ownmodel/canon.go +190 -0
- package/libs/typesafe/libraries/ownmodel/convert.go +199 -0
- package/libs/typesafe/libraries/ownmodel/doc.go +15 -0
- package/libs/typesafe/libraries/ownmodel/equivalence_test.go +199 -0
- package/libs/typesafe/libraries/ownmodel/helpers_test.go +155 -0
- package/libs/typesafe/libraries/ownmodel/mutation_test.go +31 -0
- package/libs/typesafe/libraries/ownmodel/ownmodel.go +225 -0
- package/libs/typesafe/libraries/ownmodel/plan.go +442 -0
- package/libs/typesafe/libraries/ownmodel/run.go +288 -0
- package/libs/typesafe/libraries/ownmodel/schema_test.go +254 -0
- package/libs/typesafe/libraries/ownmodel/twins_test.go +169 -0
- package/libs/typesafe/libraries/ownmodel/utils_test.go +125 -0
- package/libs/typesafe/libraries/pigmodel/pigmodel.go +264 -0
- package/libs/typesafe/libraries/pigmodel/pigmodel_test.go +410 -0
- package/libs/typesafe/libraries/typesafe/answers.go +268 -0
- package/libs/typesafe/libraries/typesafe/api_response_test.go +113 -0
- package/libs/typesafe/libraries/typesafe/batch.go +80 -0
- package/libs/typesafe/libraries/typesafe/batch_test.go +133 -0
- package/libs/typesafe/libraries/typesafe/bench_test.go +71 -0
- package/libs/typesafe/libraries/typesafe/client.go +561 -0
- package/libs/typesafe/libraries/typesafe/client_test.go +495 -0
- package/libs/typesafe/libraries/typesafe/crosscheck_test.go +464 -0
- package/libs/typesafe/libraries/typesafe/crosscheck_workflowevals_test.go +219 -0
- package/libs/typesafe/libraries/typesafe/doc.go +27 -0
- package/libs/typesafe/libraries/typesafe/entry.go +142 -0
- package/libs/typesafe/libraries/typesafe/env.go +11 -0
- package/libs/typesafe/libraries/typesafe/errors.go +310 -0
- package/libs/typesafe/libraries/typesafe/errors_test.go +175 -0
- package/libs/typesafe/libraries/typesafe/helpers_test.go +294 -0
- package/libs/typesafe/libraries/typesafe/live_test.go +96 -0
- package/libs/typesafe/libraries/typesafe/logging.go +160 -0
- package/libs/typesafe/libraries/typesafe/logging_test.go +259 -0
- package/libs/typesafe/libraries/typesafe/marshal_test.go +112 -0
- package/libs/typesafe/libraries/typesafe/mutation_test.go +39 -0
- package/libs/typesafe/libraries/typesafe/questions.go +490 -0
- package/libs/typesafe/libraries/typesafe/questions_test.go +166 -0
- package/libs/typesafe/libraries/typesafe/regressions_test.go +159 -0
- package/libs/typesafe/libraries/typesafe/reliability_test.go +649 -0
- package/libs/typesafe/libraries/typesafe/retry.go +350 -0
- package/libs/typesafe/libraries/typesafe/retry_test.go +297 -0
- package/libs/typesafe/libraries/typesafe/runtime_test.go +26 -0
- package/libs/typesafe/libraries/typesafe/transport_test.go +163 -0
- package/libs/typesafe/libraries/typesafe/twins_test.go +127 -0
- package/libs/typesafe/libraries/typesafe/types_test.go +165 -0
- package/libs/typesafe/libraries/typesafe/version.go +10 -0
- package/libs/typesafe/package.json +37 -0
- package/libs/typesafe/provenance.json +49 -0
- package/package.json +42 -0
- package/port/PORT.md +98 -0
- package/port/accepted-gaps.json +3 -0
- package/port/golden/enable-confirm.jsonl +11 -0
- package/port/golden/enable-decline.jsonl +20 -0
- package/port/golden/enable-missing-key.jsonl +4 -0
- package/port/golden/login-shadow.jsonl +4 -0
- package/port/golden/logout-env-key.jsonl +6 -0
- package/port/golden/playground-cancel.jsonl +4 -0
- package/port/golden/playground-invalid-json.jsonl +5 -0
- package/port/golden/playground-invalid-questions.jsonl +5 -0
- package/port/golden/status-env-key.jsonl +6 -0
- package/port/golden/status-no-key.jsonl +6 -0
- package/port/golden/tool-disabled.jsonl +18 -0
- package/port/golden/trailing-words.jsonl +10 -0
- package/port/library-mutations.py +44 -0
- package/port/mutations.json +302 -0
- package/port/oracle/.env.example +4 -0
- package/port/oracle/CHANGELOG.md +91 -0
- package/port/oracle/CONTRIBUTING.md +35 -0
- package/port/oracle/LICENSE +21 -0
- package/port/oracle/README.md +159 -0
- package/port/oracle/docs/api.md +143 -0
- package/port/oracle/docs/ci-cd.md +97 -0
- package/port/oracle/examples/decision-extension.ts +41 -0
- package/port/oracle/extensions/index.js +2 -0
- package/port/oracle/package.json +89 -0
- package/port/oracle/scripts/dev-pi.mjs +23 -0
- package/port/oracle/scripts/live-smoke.mjs +35 -0
- package/port/oracle/src/ask.ts +42 -0
- package/port/oracle/src/auth.ts +171 -0
- package/port/oracle/src/backends.ts +196 -0
- package/port/oracle/src/batch.ts +170 -0
- package/port/oracle/src/calibrate.ts +237 -0
- package/port/oracle/src/client.ts +310 -0
- package/port/oracle/src/credentials.ts +136 -0
- package/port/oracle/src/errors.ts +53 -0
- package/port/oracle/src/extension.ts +204 -0
- package/port/oracle/src/index.ts +31 -0
- package/port/oracle/src/key-prompt.ts +51 -0
- package/port/oracle/src/login.ts +60 -0
- package/port/oracle/src/schema.ts +158 -0
- package/port/oracle/src/ui.ts +4 -0
- package/port/oracle/src/usage.ts +258 -0
- package/port/oracle/tests/ask.test.ts +63 -0
- package/port/oracle/tests/auth.test.ts +141 -0
- package/port/oracle/tests/backends.test.ts +380 -0
- package/port/oracle/tests/batch.test.ts +156 -0
- package/port/oracle/tests/calibrate.test.ts +144 -0
- package/port/oracle/tests/client.test.ts +499 -0
- package/port/oracle/tests/credentials.test.ts +144 -0
- package/port/oracle/tests/extension.test.ts +276 -0
- package/port/oracle/tests/key-prompt.test.ts +47 -0
- package/port/oracle/tests/login.test.ts +101 -0
- package/port/oracle/tests/schema.test.ts +85 -0
- package/port/oracle/tests/usage.test.ts +106 -0
- package/port/oracle/tsconfig.build.json +10 -0
- package/port/oracle/tsconfig.json +14 -0
- package/port/scenarios/enable-confirm.json +5 -0
- package/port/scenarios/enable-decline.json +3 -0
- package/port/scenarios/enable-missing-key.json +2 -0
- package/port/scenarios/login-shadow.json +2 -0
- package/port/scenarios/logout-env-key.json +3 -0
- package/port/scenarios/playground-cancel.json +2 -0
- package/port/scenarios/playground-invalid-json.json +2 -0
- package/port/scenarios/playground-invalid-questions.json +2 -0
- package/port/scenarios/status-env-key.json +3 -0
- package/port/scenarios/status-no-key.json +3 -0
- package/port/scenarios/tool-disabled.json +2 -0
- package/port/scenarios/trailing-words.json +5 -0
- package/port/upstream-tests.json +160 -0
- package/provenance.json +18 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
package pitypesafe
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"errors"
|
|
6
|
+
"reflect"
|
|
7
|
+
"strings"
|
|
8
|
+
"sync/atomic"
|
|
9
|
+
"testing"
|
|
10
|
+
"time"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
func sample(score float64, label bool, id string) ScoredSample {
|
|
14
|
+
return ScoredSample{Score: score, Label: label, ID: id}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Eight observations: two clear positives, two clear negatives, and a muddy middle.
|
|
18
|
+
func muddy() []ScoredSample {
|
|
19
|
+
return []ScoredSample{
|
|
20
|
+
sample(0.95, true, "p1"), sample(0.8, true, "p2"), sample(0.6, true, "p3"), sample(0.4, true, "p4"),
|
|
21
|
+
sample(0.7, false, "n1"), sample(0.5, false, "n2"), sample(0.2, false, "n3"), sample(0.05, false, "n4"),
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func f(v float64) *float64 { return &v }
|
|
26
|
+
|
|
27
|
+
func TestCalibrate(t *testing.T) {
|
|
28
|
+
tw(t, "calibrate", "AUC is the rank-based probability that a positive outranks a negative", func(t *testing.T) {
|
|
29
|
+
eq := func(got *float64, want float64) {
|
|
30
|
+
t.Helper()
|
|
31
|
+
if got == nil || *got != want {
|
|
32
|
+
t.Errorf("auc = %v, want %v", got, want)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
eq(AUC([]ScoredSample{sample(1, true, ""), sample(0, false, "")}), 1)
|
|
36
|
+
eq(AUC([]ScoredSample{sample(0, true, ""), sample(1, false, "")}), 0)
|
|
37
|
+
eq(AUC([]ScoredSample{sample(0.5, true, ""), sample(0.5, false, "")}), 0.5)
|
|
38
|
+
eq(AUC([]ScoredSample{sample(0.5, true, ""), sample(0.5, true, ""), sample(0.5, false, ""), sample(0.5, false, "")}), 0.5)
|
|
39
|
+
// One class only: nothing to rank against.
|
|
40
|
+
if AUC([]ScoredSample{sample(0.5, true, ""), sample(0.9, true, "")}) != nil || AUC(nil) != nil {
|
|
41
|
+
t.Error("a single class has no AUC")
|
|
42
|
+
}
|
|
43
|
+
// Three positives above one negative and one below: 3 wins of 4 pairs.
|
|
44
|
+
eq(AUC([]ScoredSample{sample(0.9, true, ""), sample(0.8, true, ""), sample(0.7, true, ""), sample(0.6, false, ""), sample(0.1, true, "")}), 0.75)
|
|
45
|
+
})
|
|
46
|
+
tw(t, "calibrate", "a threshold row counts every case once", func(t *testing.T) {
|
|
47
|
+
row := MetricsAt(muddy(), 0.6)
|
|
48
|
+
if row.Threshold != 0.6 || row.Flagged != 4 || row.TP != 3 || row.FP != 1 || row.FN != 1 || row.TN != 3 || *row.Precision != 0.75 || *row.Recall != 0.75 || row.FlagRate != 0.5 {
|
|
49
|
+
t.Fatalf("row = %+v", row)
|
|
50
|
+
}
|
|
51
|
+
// At the top of the range nothing is flagged, so precision has no denominator.
|
|
52
|
+
strict := MetricsAt(muddy(), 1)
|
|
53
|
+
if strict.Flagged != 0 || strict.Precision != nil || *strict.Recall != 0 {
|
|
54
|
+
t.Fatalf("strict = %+v", strict)
|
|
55
|
+
}
|
|
56
|
+
if got := Sweep(muddy(), []float64{0.6}); got[0].TP != 3 {
|
|
57
|
+
t.Fatalf("sweep = %+v", got)
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
tw(t, "calibrate", "the default threshold grid is every distinct score, ascending", func(t *testing.T) {
|
|
61
|
+
if got := DefaultThresholds(muddy(), 0); !reflect.DeepEqual(got, []float64{0.05, 0.2, 0.4, 0.5, 0.6, 0.7, 0.8, 0.95}) {
|
|
62
|
+
t.Fatalf("grid = %v", got)
|
|
63
|
+
}
|
|
64
|
+
if len(DefaultThresholds(muddy(), 3)) != 3 {
|
|
65
|
+
t.Error("limit 3 must give 3 rows")
|
|
66
|
+
}
|
|
67
|
+
if got := DefaultThresholds([]ScoredSample{sample(0.5, true, ""), sample(0.5, false, "")}, 0); !reflect.DeepEqual(got, []float64{0.5}) {
|
|
68
|
+
t.Errorf("grid = %v", got)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
tw(t, "calibrate", "a recommendation honours the floors, and the report names what it misses and flags", func(t *testing.T) {
|
|
72
|
+
c := Calibrate("muddy", muddy(), CalibrateOptions{Thresholds: []float64{0.5, 0.6, 0.7, 0.8}, MinPrecision: f(0.75), MinRecall: f(0.5)})
|
|
73
|
+
if c.Recommended == nil || c.Recommended.Threshold != 0.6 || c.Scored != 8 || c.Positives != 4 || c.Negatives != 4 || c.Errors != 0 || c.AUC == nil || *c.AUC <= 0.7 {
|
|
74
|
+
t.Fatalf("calibration = %+v", c)
|
|
75
|
+
}
|
|
76
|
+
if len(c.Missed) != 1 || c.Missed[0].ID != "p4" || len(c.Flagged) != 1 || c.Flagged[0].ID != "n1" {
|
|
77
|
+
t.Fatalf("missed=%v flagged=%v", c.Missed, c.Flagged)
|
|
78
|
+
}
|
|
79
|
+
text := FormatCalibration(c)
|
|
80
|
+
for _, want := range []string{"muddy: 8 scored, 4 positives, 4 negatives", "recommended 0.60: precision 75%, recall 75%", "missed positives (1): p4", "flagged negatives (1): n1"} {
|
|
81
|
+
if !strings.Contains(text, want) {
|
|
82
|
+
t.Errorf("report lacks %q:\n%s", want, text)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Floors nothing can meet leave no recommendation, and the report says so.
|
|
86
|
+
impossible := Calibrate("strict", muddy(), CalibrateOptions{Thresholds: []float64{0.5}, MinPrecision: f(0.99), MinRecall: f(0.99)})
|
|
87
|
+
if impossible.Recommended != nil || !strings.Contains(FormatCalibration(impossible), "recommended: none") {
|
|
88
|
+
t.Fatalf("impossible = %+v", impossible)
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
tw(t, "calibrate", "with no floors, the recommendation is the best F1 among the rows", func(t *testing.T) {
|
|
92
|
+
rows := Sweep(muddy(), []float64{0.4, 0.5, 0.6, 0.7})
|
|
93
|
+
var tps []int
|
|
94
|
+
for _, r := range rows {
|
|
95
|
+
tps = append(tps, r.TP)
|
|
96
|
+
}
|
|
97
|
+
// At 0.4 every positive is caught for two false alarms: F1 0.8 beats the tighter rows.
|
|
98
|
+
if !reflect.DeepEqual(tps, []int{4, 3, 3, 2}) {
|
|
99
|
+
t.Fatalf("tp = %v", tps)
|
|
100
|
+
}
|
|
101
|
+
if got := PickThreshold(rows, nil, nil); got == nil || got.Threshold != 0.4 {
|
|
102
|
+
t.Fatalf("pick = %+v", got)
|
|
103
|
+
}
|
|
104
|
+
if PickThreshold(nil, nil, nil) != nil {
|
|
105
|
+
t.Error("no rows, no pick")
|
|
106
|
+
}
|
|
107
|
+
// No positives: precision has no denominator, so no row can be ranked.
|
|
108
|
+
if PickThreshold(Sweep([]ScoredSample{sample(0.5, false, "")}, []float64{0.5}), nil, nil) != nil {
|
|
109
|
+
t.Error("no positives, no pick")
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
tw(t, "calibrate", "replay scores labelled cases with bounded concurrency and keeps per-case order", func(t *testing.T) {
|
|
113
|
+
var inFlight, peak atomic.Int32
|
|
114
|
+
cases := []ReplayCase[int]{{"a", true, 1}, {"b", false, 2}, {"c", true, 3}, {"d", false, 4}}
|
|
115
|
+
results := Replay(context.Background(), cases, func(_ context.Context, data int, _ int) (float64, error) {
|
|
116
|
+
n := inFlight.Add(1)
|
|
117
|
+
for {
|
|
118
|
+
p := peak.Load()
|
|
119
|
+
if n <= p || peak.CompareAndSwap(p, n) {
|
|
120
|
+
break
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
time.Sleep(5 * time.Millisecond)
|
|
124
|
+
inFlight.Add(-1)
|
|
125
|
+
return float64(data), nil
|
|
126
|
+
}, ReplayOptions{Concurrency: 2})
|
|
127
|
+
if peak.Load() != 2 {
|
|
128
|
+
t.Errorf("peak = %d", peak.Load())
|
|
129
|
+
}
|
|
130
|
+
for i, r := range results {
|
|
131
|
+
if r.ID != cases[i].ID || r.Score != float64(i+1) || r.Skipped || r.Error != "" {
|
|
132
|
+
t.Errorf("result %d = %+v", i, r)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if samples, errs := SamplesOf(results); errs != 0 || len(samples) != 4 {
|
|
136
|
+
t.Errorf("samples = %v, %d", samples, errs)
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
tw(t, "calibrate", "a budget failure stops the replay and leaves the rest unsubmitted", func(t *testing.T) {
|
|
140
|
+
var started []int
|
|
141
|
+
cases := []ReplayCase[int]{{"a", true, 1}, {"b", false, 2}, {"c", true, 3}, {"d", false, 4}}
|
|
142
|
+
msg := "TypeSafe request limit reached (1 attempts per client instance)."
|
|
143
|
+
results := Replay(context.Background(), cases, func(_ context.Context, data int, _ int) (float64, error) {
|
|
144
|
+
started = append(started, data)
|
|
145
|
+
if data == 2 {
|
|
146
|
+
return 0, newError(CodeBudget, msg)
|
|
147
|
+
}
|
|
148
|
+
return 0.1, nil
|
|
149
|
+
}, ReplayOptions{Concurrency: 1, StopOn: func(err error) bool { return hasCode(err, CodeBudget) }})
|
|
150
|
+
if !reflect.DeepEqual(started, []int{1, 2}) || results[1].Error != msg || results[1].Skipped {
|
|
151
|
+
t.Fatalf("started=%v r1=%+v", started, results[1])
|
|
152
|
+
}
|
|
153
|
+
// The budget stop kept every later case from being submitted at all.
|
|
154
|
+
if !results[2].Skipped || !results[3].Skipped || results[3].Scored {
|
|
155
|
+
t.Fatalf("r2=%+v r3=%+v", results[2], results[3])
|
|
156
|
+
}
|
|
157
|
+
samples, errs := SamplesOf(results)
|
|
158
|
+
if errs != 3 || !reflect.DeepEqual(samples, []ScoredSample{{Label: true, Score: 0.1, ID: "a"}}) {
|
|
159
|
+
t.Fatalf("samples = %v, %d", samples, errs)
|
|
160
|
+
}
|
|
161
|
+
// A replay that lost most of its cases still yields honest numbers.
|
|
162
|
+
c := Calibrate("replay", samples, CalibrateOptions{Errors: errs})
|
|
163
|
+
if c.Errors != 3 || c.Scored != 1 || c.AUC != nil {
|
|
164
|
+
t.Fatalf("calibration = %+v", c)
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
tw(t, "calibrate", "replay reports scorer failures with the caller's own message", func(t *testing.T) {
|
|
168
|
+
thrown := errors.New("upstream body with a key: sk-secret")
|
|
169
|
+
one := []ReplayCase[string]{{"boom", true, "case"}}
|
|
170
|
+
fail := func(context.Context, string, int) (float64, error) { return 0, thrown }
|
|
171
|
+
// The default is the scorer's own message, which the caller controls; a supplied describer replaces it.
|
|
172
|
+
plain := Replay(context.Background(), one, fail, ReplayOptions{})
|
|
173
|
+
if plain[0].Error != "upstream body with a key: sk-secret" || plain[0].Scored {
|
|
174
|
+
t.Fatalf("plain = %+v", plain[0])
|
|
175
|
+
}
|
|
176
|
+
described := Replay(context.Background(), one, fail, ReplayOptions{DescribeError: func(error) string { return "The scorer failed." }})
|
|
177
|
+
if described[0].Error != "The scorer failed." {
|
|
178
|
+
t.Fatalf("described = %+v", described[0])
|
|
179
|
+
}
|
|
180
|
+
// An error with no message reads as the generic failure (a thrown non-Error in the original).
|
|
181
|
+
blank := Replay(context.Background(), one, func(context.Context, string, int) (float64, error) { return 0, errors.New("") }, ReplayOptions{})
|
|
182
|
+
if blank[0].Error != "The scorer failed." {
|
|
183
|
+
t.Fatalf("blank = %+v", blank[0])
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
}
|