All checks were successful
ci/woodpecker/tag/release Pipeline was successful
1033 lines
34 KiB
Go
1033 lines
34 KiB
Go
package seriatim
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestSubprocessRunnerSuccessWithReportArgsAndEnv(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "success")
|
|
|
|
recordPath := filepath.Join(t.TempDir(), "record.json")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", recordPath)
|
|
|
|
wrapper := writeHelperWrapper(t)
|
|
coalesce := 3.0
|
|
owg := 1.0
|
|
owrw := 1.0
|
|
bmd := 2.0
|
|
fmd := 1.25
|
|
runner, err := NewSubprocessRunner(SubprocessRunnerConfig{
|
|
Binary: wrapper,
|
|
Timeout: mustParseDuration(t, "2s"),
|
|
OutputSchema: "seriatim-intermediate",
|
|
CoalesceGap: &coalesce,
|
|
Report: true,
|
|
Env: EnvConfig{
|
|
OverlapWordRunGap: &owg,
|
|
OverlapWordRunReorderWindow: &owrw,
|
|
BackchannelMaxDuration: &bmd,
|
|
FillerMaxDuration: &fmd,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSubprocessRunner() error = %v", err)
|
|
}
|
|
|
|
dir := t.TempDir()
|
|
req := MergeRequest{
|
|
GeneratedConfigPath: filepath.Join(dir, "seriatim.generated.yml"),
|
|
InputTranscriptPaths: []string{filepath.Join(dir, "a.json"), filepath.Join(dir, "b.json")},
|
|
OutputMergedTranscriptPath: filepath.Join(dir, "base.json"),
|
|
ReportPath: filepath.Join(dir, "seriatim.report.json"),
|
|
SpeakersPath: filepath.Join(dir, "speakers.yml"),
|
|
AutocorrectPath: filepath.Join(dir, "autocorrect.yml"),
|
|
StdoutLogPath: filepath.Join(dir, "seriatim.stdout.log"),
|
|
StderrLogPath: filepath.Join(dir, "seriatim.stderr.log"),
|
|
}
|
|
writeSeriatimFile(t, req.InputTranscriptPaths[0], `{"a":1}`)
|
|
writeSeriatimFile(t, req.InputTranscriptPaths[1], `{"b":1}`)
|
|
writeSeriatimFile(t, req.SpeakersPath, "speakers: []\n")
|
|
writeSeriatimFile(t, req.AutocorrectPath, "autocorrect: []\n")
|
|
|
|
res, err := runner.Run(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatalf("Run() error = %v", err)
|
|
}
|
|
if res.MergedTranscriptPath != req.OutputMergedTranscriptPath {
|
|
t.Fatalf("MergedTranscriptPath = %q, want %q", res.MergedTranscriptPath, req.OutputMergedTranscriptPath)
|
|
}
|
|
if res.ReportPath != req.ReportPath {
|
|
t.Fatalf("ReportPath = %q, want %q", res.ReportPath, req.ReportPath)
|
|
}
|
|
if res.OutputSchema != "seriatim-intermediate" {
|
|
t.Fatalf("OutputSchema = %q, want seriatim-intermediate", res.OutputSchema)
|
|
}
|
|
if res.InvokedBinary != wrapper {
|
|
t.Fatalf("InvokedBinary = %q, want %q", res.InvokedBinary, wrapper)
|
|
}
|
|
if res.ExitCode != 0 {
|
|
t.Fatalf("ExitCode = %d, want 0", res.ExitCode)
|
|
}
|
|
if res.Duration <= 0 {
|
|
t.Fatalf("Duration = %s, want >0", res.Duration)
|
|
}
|
|
if res.Metadata == nil || res.Metadata["adapter"] != "seriatim_subprocess" {
|
|
t.Fatalf("Metadata = %#v, want adapter marker", res.Metadata)
|
|
}
|
|
|
|
assertJSONFile(t, req.OutputMergedTranscriptPath)
|
|
assertJSONFile(t, req.ReportPath)
|
|
|
|
if _, err := os.Stat(req.GeneratedConfigPath); err != nil {
|
|
t.Fatalf("generated config missing: %v", err)
|
|
}
|
|
if _, err := os.Stat(req.StdoutLogPath); err != nil {
|
|
t.Fatalf("stdout log missing: %v", err)
|
|
}
|
|
if _, err := os.Stat(req.StderrLogPath); err != nil {
|
|
t.Fatalf("stderr log missing: %v", err)
|
|
}
|
|
|
|
rec := readHelperRecord(t, recordPath)
|
|
wantArgs := []string{
|
|
"merge",
|
|
"--input-file", req.InputTranscriptPaths[0],
|
|
"--input-file", req.InputTranscriptPaths[1],
|
|
"--output-file", req.OutputMergedTranscriptPath,
|
|
"--report-file", req.ReportPath,
|
|
"--speakers", req.SpeakersPath,
|
|
"--autocorrect", req.AutocorrectPath,
|
|
"--output-schema", "seriatim-intermediate",
|
|
"--coalesce-gap", "3",
|
|
}
|
|
if strings.Join(rec.Args, "\n") != strings.Join(wantArgs, "\n") {
|
|
t.Fatalf("args = %#v, want %#v", rec.Args, wantArgs)
|
|
}
|
|
if rec.Env["SERIATIM_OVERLAP_WORD_RUN_GAP"] != "1" {
|
|
t.Fatalf("SERIATIM_OVERLAP_WORD_RUN_GAP = %q, want 1", rec.Env["SERIATIM_OVERLAP_WORD_RUN_GAP"])
|
|
}
|
|
if rec.Env["SERIATIM_OVERLAP_WORD_RUN_REORDER_WINDOW"] != "1" {
|
|
t.Fatalf("SERIATIM_OVERLAP_WORD_RUN_REORDER_WINDOW = %q, want 1", rec.Env["SERIATIM_OVERLAP_WORD_RUN_REORDER_WINDOW"])
|
|
}
|
|
if rec.Env["SERIATIM_BACKCHANNEL_MAX_DURATION"] != "2" {
|
|
t.Fatalf("SERIATIM_BACKCHANNEL_MAX_DURATION = %q, want 2", rec.Env["SERIATIM_BACKCHANNEL_MAX_DURATION"])
|
|
}
|
|
if rec.Env["SERIATIM_FILLER_MAX_DURATION"] != "1.25" {
|
|
t.Fatalf("SERIATIM_FILLER_MAX_DURATION = %q, want 1.25", rec.Env["SERIATIM_FILLER_MAX_DURATION"])
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerSubprocessFailure(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "fail")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
|
|
|
runner := mustRunner(t, writeHelperWrapper(t), false)
|
|
req := mergeReqForTest(t, false)
|
|
_, err := runner.Run(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("Run() error = nil, want non-nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "run seriatim merge") {
|
|
t.Fatalf("error = %q, want subprocess context", err.Error())
|
|
}
|
|
if !strings.Contains(err.Error(), "exit code") {
|
|
t.Fatalf("error = %q, want exit code context", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerMissingOutputFails(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "missing_output")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
|
|
|
runner := mustRunner(t, writeHelperWrapper(t), false)
|
|
req := mergeReqForTest(t, false)
|
|
_, err := runner.Run(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("Run() error = nil, want non-nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "validate seriatim merged output") {
|
|
t.Fatalf("error = %q, want output validation context", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerInvalidOutputJSONFails(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "invalid_output")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
|
|
|
runner := mustRunner(t, writeHelperWrapper(t), false)
|
|
req := mergeReqForTest(t, false)
|
|
_, err := runner.Run(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("Run() error = nil, want non-nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "parse json") {
|
|
t.Fatalf("error = %q, want parse json context", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerInvalidReportJSONFails(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "invalid_report")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
|
|
|
runner := mustRunner(t, writeHelperWrapper(t), true)
|
|
req := mergeReqForTest(t, true)
|
|
_, err := runner.Run(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("Run() error = nil, want non-nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "validate seriatim report output") {
|
|
t.Fatalf("error = %q, want report validation context", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerTrimSuccessInvocationAndProvenance(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "trim_success")
|
|
recordPath := filepath.Join(t.TempDir(), "record.json")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", recordPath)
|
|
|
|
wrapper := writeHelperWrapper(t)
|
|
runner := mustRunner(t, wrapper, false)
|
|
req := trimReqForTest(t)
|
|
|
|
res, err := runner.Trim(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatalf("Trim() error = %v", err)
|
|
}
|
|
if res.OutputTrimmedPath != req.OutputTrimmedPath {
|
|
t.Fatalf("OutputTrimmedPath = %q, want %q", res.OutputTrimmedPath, req.OutputTrimmedPath)
|
|
}
|
|
if res.KeepSelector != req.KeepSelector {
|
|
t.Fatalf("KeepSelector = %q, want %q", res.KeepSelector, req.KeepSelector)
|
|
}
|
|
if res.InvokedBinary != wrapper {
|
|
t.Fatalf("InvokedBinary = %q, want %q", res.InvokedBinary, wrapper)
|
|
}
|
|
if res.ExitCode != 0 {
|
|
t.Fatalf("ExitCode = %d, want 0", res.ExitCode)
|
|
}
|
|
if res.Duration <= 0 {
|
|
t.Fatalf("Duration = %s, want >0", res.Duration)
|
|
}
|
|
if res.Metadata == nil || res.Metadata["adapter"] != "seriatim_subprocess" {
|
|
t.Fatalf("Metadata = %#v, want adapter marker", res.Metadata)
|
|
}
|
|
|
|
assertJSONFile(t, req.OutputTrimmedPath)
|
|
if _, err := os.Stat(req.StdoutLogPath); err != nil {
|
|
t.Fatalf("stdout log missing: %v", err)
|
|
}
|
|
if _, err := os.Stat(req.StderrLogPath); err != nil {
|
|
t.Fatalf("stderr log missing: %v", err)
|
|
}
|
|
if _, err := os.Stat(req.GeneratedConfigPath); err != nil {
|
|
t.Fatalf("generated config missing: %v", err)
|
|
}
|
|
|
|
stdoutData, err := os.ReadFile(req.StdoutLogPath)
|
|
if err != nil {
|
|
t.Fatalf("read stdout log: %v", err)
|
|
}
|
|
if !strings.Contains(string(stdoutData), "seriatim helper trim stdout") {
|
|
t.Fatalf("stdout log = %q, want trim helper stdout marker", string(stdoutData))
|
|
}
|
|
stderrData, err := os.ReadFile(req.StderrLogPath)
|
|
if err != nil {
|
|
t.Fatalf("read stderr log: %v", err)
|
|
}
|
|
if !strings.Contains(string(stderrData), "seriatim helper trim stderr") {
|
|
t.Fatalf("stderr log = %q, want trim helper stderr marker", string(stderrData))
|
|
}
|
|
|
|
cfgData, err := os.ReadFile(req.GeneratedConfigPath)
|
|
if err != nil {
|
|
t.Fatalf("read generated config: %v", err)
|
|
}
|
|
cfgText := string(cfgData)
|
|
if !strings.Contains(cfgText, "command: trim") {
|
|
t.Fatalf("generated config = %q, want command: trim", cfgText)
|
|
}
|
|
if !strings.Contains(cfgText, "keep_selector: 5-12") {
|
|
t.Fatalf("generated config = %q, want keep selector", cfgText)
|
|
}
|
|
|
|
rec := readHelperRecord(t, recordPath)
|
|
wantArgs := []string{
|
|
"trim",
|
|
"--input-file", req.InputTranscriptPath,
|
|
"--output-file", req.OutputTrimmedPath,
|
|
"--keep", req.KeepSelector,
|
|
}
|
|
if strings.Join(rec.Args, "\n") != strings.Join(wantArgs, "\n") {
|
|
t.Fatalf("args = %#v, want %#v", rec.Args, wantArgs)
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerTrimSubprocessFailure(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "fail")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
|
|
|
runner := mustRunner(t, writeHelperWrapper(t), false)
|
|
req := trimReqForTest(t)
|
|
_, err := runner.Trim(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("Trim() error = nil, want non-nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "run seriatim trim") {
|
|
t.Fatalf("error = %q, want subprocess context", err.Error())
|
|
}
|
|
if !strings.Contains(err.Error(), "exit code") {
|
|
t.Fatalf("error = %q, want exit code context", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerTrimMissingOutputFails(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "missing_output")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
|
|
|
runner := mustRunner(t, writeHelperWrapper(t), false)
|
|
req := trimReqForTest(t)
|
|
_, err := runner.Trim(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("Trim() error = nil, want non-nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "validate seriatim trimmed output") {
|
|
t.Fatalf("error = %q, want output validation context", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerTrimInvalidOutputJSONFails(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "invalid_output")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
|
|
|
runner := mustRunner(t, writeHelperWrapper(t), false)
|
|
req := trimReqForTest(t)
|
|
_, err := runner.Trim(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("Trim() error = nil, want non-nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "parse json") {
|
|
t.Fatalf("error = %q, want parse json context", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerTrimOutputMissingSegmentsFails(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "trim_missing_segments")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
|
|
|
runner := mustRunner(t, writeHelperWrapper(t), false)
|
|
req := trimReqForTest(t)
|
|
_, err := runner.Trim(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("Trim() error = nil, want non-nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "top-level segments is required") {
|
|
t.Fatalf("error = %q, want missing segments context", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerNormalizeSuccessInvocationAndProvenance(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "normalize_success")
|
|
recordPath := filepath.Join(t.TempDir(), "record.json")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", recordPath)
|
|
|
|
wrapper := writeHelperWrapper(t)
|
|
runner := mustRunner(t, wrapper, false)
|
|
req := normalizeReqForTest(t, true)
|
|
|
|
res, err := runner.Normalize(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatalf("Normalize() error = %v", err)
|
|
}
|
|
if res.OutputNormalizedPath != req.OutputNormalizedPath {
|
|
t.Fatalf("OutputNormalizedPath = %q, want %q", res.OutputNormalizedPath, req.OutputNormalizedPath)
|
|
}
|
|
if res.OutputSchema != req.OutputSchema {
|
|
t.Fatalf("OutputSchema = %q, want %q", res.OutputSchema, req.OutputSchema)
|
|
}
|
|
if res.ReportPath != req.ReportPath {
|
|
t.Fatalf("ReportPath = %q, want %q", res.ReportPath, req.ReportPath)
|
|
}
|
|
if res.InvokedBinary != wrapper {
|
|
t.Fatalf("InvokedBinary = %q, want %q", res.InvokedBinary, wrapper)
|
|
}
|
|
if res.ExitCode != 0 {
|
|
t.Fatalf("ExitCode = %d, want 0", res.ExitCode)
|
|
}
|
|
if res.Duration <= 0 {
|
|
t.Fatalf("Duration = %s, want >0", res.Duration)
|
|
}
|
|
if res.Metadata == nil || res.Metadata["adapter"] != "seriatim_subprocess" {
|
|
t.Fatalf("Metadata = %#v, want adapter marker", res.Metadata)
|
|
}
|
|
|
|
assertJSONFile(t, req.OutputNormalizedPath)
|
|
assertJSONFile(t, req.ReportPath)
|
|
if _, err := os.Stat(req.StdoutLogPath); err != nil {
|
|
t.Fatalf("stdout log missing: %v", err)
|
|
}
|
|
if _, err := os.Stat(req.StderrLogPath); err != nil {
|
|
t.Fatalf("stderr log missing: %v", err)
|
|
}
|
|
if _, err := os.Stat(req.GeneratedConfigPath); err != nil {
|
|
t.Fatalf("generated config missing: %v", err)
|
|
}
|
|
|
|
cfgData, err := os.ReadFile(req.GeneratedConfigPath)
|
|
if err != nil {
|
|
t.Fatalf("read generated config: %v", err)
|
|
}
|
|
cfgText := string(cfgData)
|
|
if !strings.Contains(cfgText, "command: normalize") {
|
|
t.Fatalf("generated config = %q, want command: normalize", cfgText)
|
|
}
|
|
if !strings.Contains(cfgText, "output_schema: seriatim-intermediate") {
|
|
t.Fatalf("generated config = %q, want output schema", cfgText)
|
|
}
|
|
|
|
rec := readHelperRecord(t, recordPath)
|
|
wantArgs := []string{
|
|
"normalize",
|
|
"--input-file", req.InputTranscriptPath,
|
|
"--output-file", req.OutputNormalizedPath,
|
|
"--output-schema", req.OutputSchema,
|
|
"--report-file", req.ReportPath,
|
|
}
|
|
if strings.Join(rec.Args, "\n") != strings.Join(wantArgs, "\n") {
|
|
t.Fatalf("args = %#v, want %#v", rec.Args, wantArgs)
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerNormalizeSubprocessFailure(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "fail")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
|
|
|
runner := mustRunner(t, writeHelperWrapper(t), false)
|
|
req := normalizeReqForTest(t, false)
|
|
_, err := runner.Normalize(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("Normalize() error = nil, want non-nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "run seriatim normalize") {
|
|
t.Fatalf("error = %q, want subprocess context", err.Error())
|
|
}
|
|
if !strings.Contains(err.Error(), "exit code") {
|
|
t.Fatalf("error = %q, want exit code context", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerNormalizeMissingOutputFails(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "normalize_missing_output")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
|
|
|
runner := mustRunner(t, writeHelperWrapper(t), false)
|
|
req := normalizeReqForTest(t, false)
|
|
_, err := runner.Normalize(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("Normalize() error = nil, want non-nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "validate seriatim normalized output") {
|
|
t.Fatalf("error = %q, want output validation context", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerNormalizeInvalidOutputJSONFails(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "normalize_invalid_output")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
|
|
|
runner := mustRunner(t, writeHelperWrapper(t), false)
|
|
req := normalizeReqForTest(t, false)
|
|
_, err := runner.Normalize(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("Normalize() error = nil, want non-nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "parse json") {
|
|
t.Fatalf("error = %q, want parse json context", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerNormalizeOutputMissingSegmentsFails(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "normalize_missing_segments")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
|
|
|
runner := mustRunner(t, writeHelperWrapper(t), false)
|
|
req := normalizeReqForTest(t, false)
|
|
_, err := runner.Normalize(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("Normalize() error = nil, want non-nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "top-level segments is required") {
|
|
t.Fatalf("error = %q, want missing segments context", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerNormalizeReportMissingFails(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "normalize_report_missing")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
|
|
|
runner := mustRunner(t, writeHelperWrapper(t), false)
|
|
req := normalizeReqForTest(t, true)
|
|
_, err := runner.Normalize(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("Normalize() error = nil, want non-nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "validate seriatim normalize report output") {
|
|
t.Fatalf("error = %q, want report validation context", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerNormalizeInvalidReportJSONFails(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "normalize_invalid_report")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
|
|
|
runner := mustRunner(t, writeHelperWrapper(t), false)
|
|
req := normalizeReqForTest(t, true)
|
|
_, err := runner.Normalize(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("Normalize() error = nil, want non-nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "validate seriatim normalize report output") {
|
|
t.Fatalf("error = %q, want report validation context", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerRenderSuccessInvocationAndProvenance(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "render_success")
|
|
recordPath := filepath.Join(t.TempDir(), "record.json")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", recordPath)
|
|
|
|
wrapper := writeHelperWrapper(t)
|
|
runner := mustRunner(t, wrapper, false)
|
|
req := renderReqForTest(t)
|
|
|
|
res, err := runner.Render(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatalf("Render() error = %v", err)
|
|
}
|
|
if res.OutputRenderedPath != req.OutputRenderedPath {
|
|
t.Fatalf("OutputRenderedPath = %q, want %q", res.OutputRenderedPath, req.OutputRenderedPath)
|
|
}
|
|
if res.Format != req.Format {
|
|
t.Fatalf("Format = %q, want %q", res.Format, req.Format)
|
|
}
|
|
if res.Title != req.Title {
|
|
t.Fatalf("Title = %q, want %q", res.Title, req.Title)
|
|
}
|
|
if res.InvokedBinary != wrapper {
|
|
t.Fatalf("InvokedBinary = %q, want %q", res.InvokedBinary, wrapper)
|
|
}
|
|
if res.ExitCode != 0 {
|
|
t.Fatalf("ExitCode = %d, want 0", res.ExitCode)
|
|
}
|
|
if res.Duration <= 0 {
|
|
t.Fatalf("Duration = %s, want >0", res.Duration)
|
|
}
|
|
if res.Metadata == nil || res.Metadata["adapter"] != "seriatim_subprocess" {
|
|
t.Fatalf("Metadata = %#v, want adapter marker", res.Metadata)
|
|
}
|
|
|
|
if _, err := os.Stat(req.OutputRenderedPath); err != nil {
|
|
t.Fatalf("rendered output missing: %v", err)
|
|
}
|
|
if _, err := os.Stat(req.StdoutLogPath); err != nil {
|
|
t.Fatalf("stdout log missing: %v", err)
|
|
}
|
|
if _, err := os.Stat(req.StderrLogPath); err != nil {
|
|
t.Fatalf("stderr log missing: %v", err)
|
|
}
|
|
if _, err := os.Stat(req.GeneratedConfigPath); err != nil {
|
|
t.Fatalf("generated config missing: %v", err)
|
|
}
|
|
|
|
rec := readHelperRecord(t, recordPath)
|
|
wantArgs := []string{
|
|
"render",
|
|
"--input-file", req.InputTranscriptPath,
|
|
"--output-file", req.OutputRenderedPath,
|
|
"--format", req.Format,
|
|
"--include-timestamps=true",
|
|
"--include-segment-ids=true",
|
|
"--include-metadata=false",
|
|
"--title", req.Title,
|
|
}
|
|
if strings.Join(rec.Args, "\n") != strings.Join(wantArgs, "\n") {
|
|
t.Fatalf("args = %#v, want %#v", rec.Args, wantArgs)
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerRenderWithoutTitleOmitsTitleArg(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "render_success")
|
|
recordPath := filepath.Join(t.TempDir(), "record.json")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", recordPath)
|
|
|
|
runner := mustRunner(t, writeHelperWrapper(t), false)
|
|
req := renderReqForTest(t)
|
|
req.Title = ""
|
|
if _, err := runner.Render(context.Background(), req); err != nil {
|
|
t.Fatalf("Render() error = %v", err)
|
|
}
|
|
|
|
rec := readHelperRecord(t, recordPath)
|
|
for i := 0; i < len(rec.Args); i++ {
|
|
if rec.Args[i] == "--title" {
|
|
t.Fatalf("args = %#v, did not expect --title", rec.Args)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerRenderSubprocessFailure(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "fail")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
|
|
|
runner := mustRunner(t, writeHelperWrapper(t), false)
|
|
req := renderReqForTest(t)
|
|
_, err := runner.Render(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("Render() error = nil, want non-nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "run seriatim render") {
|
|
t.Fatalf("error = %q, want subprocess context", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerRenderMissingOutputFails(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "missing_output")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
|
|
|
runner := mustRunner(t, writeHelperWrapper(t), false)
|
|
req := renderReqForTest(t)
|
|
_, err := runner.Render(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("Render() error = nil, want non-nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "validate seriatim rendered output") {
|
|
t.Fatalf("error = %q, want output validation context", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerRenderEmptyOutputFails(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("helper wrapper script uses /bin/sh")
|
|
}
|
|
t.Setenv("GO_WANT_SERIATIM_HELPER", "1")
|
|
t.Setenv("SERIATIM_HELPER_MODE", "render_empty_output")
|
|
t.Setenv("SERIATIM_HELPER_RECORD_PATH", filepath.Join(t.TempDir(), "record.json"))
|
|
|
|
runner := mustRunner(t, writeHelperWrapper(t), false)
|
|
req := renderReqForTest(t)
|
|
_, err := runner.Render(context.Background(), req)
|
|
if err == nil {
|
|
t.Fatal("Render() error = nil, want non-nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "file is empty") {
|
|
t.Fatalf("error = %q, want empty-file validation", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSubprocessRunnerConstructorValidation(t *testing.T) {
|
|
_, err := NewSubprocessRunnerFromConfigValues("", "10m", "seriatim-intermediate", nil, true, EnvConfig{})
|
|
if err == nil {
|
|
t.Fatal("expected binary validation error")
|
|
}
|
|
_, err = NewSubprocessRunnerFromConfigValues("seriatim", "bad", "seriatim-intermediate", nil, true, EnvConfig{})
|
|
if err == nil {
|
|
t.Fatal("expected timeout parse error")
|
|
}
|
|
_, err = NewSubprocessRunner(SubprocessRunnerConfig{
|
|
Binary: "seriatim",
|
|
Timeout: mustParseDuration(t, "1s"),
|
|
OutputSchema: "bad-schema",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected output schema validation error")
|
|
}
|
|
}
|
|
|
|
type helperRecord struct {
|
|
Args []string `json:"args"`
|
|
Env map[string]string `json:"env"`
|
|
}
|
|
|
|
func TestSeriatimSubprocessHelper(t *testing.T) {
|
|
if os.Getenv("GO_WANT_SERIATIM_HELPER") != "1" {
|
|
return
|
|
}
|
|
|
|
args := os.Args
|
|
start := -1
|
|
for i := range args {
|
|
if args[i] == "--" {
|
|
start = i + 1
|
|
break
|
|
}
|
|
}
|
|
if start < 0 || start >= len(args) {
|
|
_, _ = os.Stderr.WriteString("missing -- args separator\n")
|
|
os.Exit(2)
|
|
}
|
|
cmdArgs := args[start:]
|
|
|
|
outputPath := flagValue(cmdArgs, "--output-file")
|
|
reportPath := flagValue(cmdArgs, "--report-file")
|
|
recordPath := os.Getenv("SERIATIM_HELPER_RECORD_PATH")
|
|
if strings.TrimSpace(recordPath) != "" {
|
|
rec := helperRecord{
|
|
Args: cmdArgs,
|
|
Env: map[string]string{
|
|
"SERIATIM_OVERLAP_WORD_RUN_GAP": os.Getenv("SERIATIM_OVERLAP_WORD_RUN_GAP"),
|
|
"SERIATIM_OVERLAP_WORD_RUN_REORDER_WINDOW": os.Getenv("SERIATIM_OVERLAP_WORD_RUN_REORDER_WINDOW"),
|
|
"SERIATIM_BACKCHANNEL_MAX_DURATION": os.Getenv("SERIATIM_BACKCHANNEL_MAX_DURATION"),
|
|
"SERIATIM_FILLER_MAX_DURATION": os.Getenv("SERIATIM_FILLER_MAX_DURATION"),
|
|
},
|
|
}
|
|
data, _ := json.Marshal(rec)
|
|
_ = os.MkdirAll(filepath.Dir(recordPath), 0o755)
|
|
_ = os.WriteFile(recordPath, data, 0o644)
|
|
}
|
|
|
|
mode := os.Getenv("SERIATIM_HELPER_MODE")
|
|
switch mode {
|
|
case "success":
|
|
writeSeriatimHelperFile(outputPath, `{"merged":true}`)
|
|
if reportPath != "" {
|
|
writeSeriatimHelperFile(reportPath, `{"report":true}`)
|
|
}
|
|
_, _ = os.Stdout.WriteString("seriatim helper success stdout\n")
|
|
_, _ = os.Stderr.WriteString("seriatim helper success stderr\n")
|
|
os.Exit(0)
|
|
case "trim_success":
|
|
writeSeriatimHelperFile(outputPath, `{"schema":"seriatim.intermediate.v1","segments":[]}`)
|
|
_, _ = os.Stdout.WriteString("seriatim helper trim stdout\n")
|
|
_, _ = os.Stderr.WriteString("seriatim helper trim stderr\n")
|
|
os.Exit(0)
|
|
case "normalize_success":
|
|
writeSeriatimHelperFile(outputPath, `{"schema":"seriatim.intermediate.v1","segments":[]}`)
|
|
if reportPath != "" {
|
|
writeSeriatimHelperFile(reportPath, `{"schema":"seriatim.normalize.report.v1","ok":true}`)
|
|
}
|
|
_, _ = os.Stdout.WriteString("seriatim helper normalize stdout\n")
|
|
_, _ = os.Stderr.WriteString("seriatim helper normalize stderr\n")
|
|
os.Exit(0)
|
|
case "fail":
|
|
_, _ = os.Stderr.WriteString("seriatim helper failure\n")
|
|
os.Exit(9)
|
|
case "missing_output":
|
|
if reportPath != "" {
|
|
writeSeriatimHelperFile(reportPath, `{"report":true}`)
|
|
}
|
|
os.Exit(0)
|
|
case "normalize_missing_output":
|
|
if reportPath != "" {
|
|
writeSeriatimHelperFile(reportPath, `{"schema":"seriatim.normalize.report.v1","ok":true}`)
|
|
}
|
|
os.Exit(0)
|
|
case "invalid_output":
|
|
writeSeriatimHelperFile(outputPath, `not-json`)
|
|
if reportPath != "" {
|
|
writeSeriatimHelperFile(reportPath, `{"report":true}`)
|
|
}
|
|
os.Exit(0)
|
|
case "normalize_invalid_output":
|
|
writeSeriatimHelperFile(outputPath, `not-json`)
|
|
if reportPath != "" {
|
|
writeSeriatimHelperFile(reportPath, `{"schema":"seriatim.normalize.report.v1","ok":true}`)
|
|
}
|
|
os.Exit(0)
|
|
case "invalid_report":
|
|
writeSeriatimHelperFile(outputPath, `{"merged":true}`)
|
|
if reportPath != "" {
|
|
writeSeriatimHelperFile(reportPath, `not-json`)
|
|
}
|
|
os.Exit(0)
|
|
case "normalize_invalid_report":
|
|
writeSeriatimHelperFile(outputPath, `{"schema":"seriatim.intermediate.v1","segments":[]}`)
|
|
if reportPath != "" {
|
|
writeSeriatimHelperFile(reportPath, `not-json`)
|
|
}
|
|
os.Exit(0)
|
|
case "trim_missing_segments":
|
|
writeSeriatimHelperFile(outputPath, `{"schema":"seriatim.intermediate.v1"}`)
|
|
os.Exit(0)
|
|
case "normalize_missing_segments":
|
|
writeSeriatimHelperFile(outputPath, `{"schema":"seriatim.intermediate.v1"}`)
|
|
if reportPath != "" {
|
|
writeSeriatimHelperFile(reportPath, `{"schema":"seriatim.normalize.report.v1","ok":true}`)
|
|
}
|
|
os.Exit(0)
|
|
case "normalize_report_missing":
|
|
writeSeriatimHelperFile(outputPath, `{"schema":"seriatim.intermediate.v1","segments":[]}`)
|
|
os.Exit(0)
|
|
case "render_success":
|
|
writeSeriatimHelperFile(outputPath, "# Rendered transcript\n\nHello.\n")
|
|
_, _ = os.Stdout.WriteString("seriatim helper render stdout\n")
|
|
_, _ = os.Stderr.WriteString("seriatim helper render stderr\n")
|
|
os.Exit(0)
|
|
case "render_empty_output":
|
|
writeSeriatimHelperFile(outputPath, "")
|
|
os.Exit(0)
|
|
default:
|
|
_, _ = os.Stderr.WriteString(fmt.Sprintf("unknown helper mode %q\n", mode))
|
|
os.Exit(2)
|
|
}
|
|
}
|
|
|
|
func writeHelperWrapper(t *testing.T) string {
|
|
t.Helper()
|
|
exe, err := os.Executable()
|
|
if err != nil {
|
|
t.Fatalf("os.Executable() error = %v", err)
|
|
}
|
|
path := filepath.Join(t.TempDir(), "seriatim-helper-wrapper.sh")
|
|
content := "#!/bin/sh\nexec \"" + exe + "\" -test.run=TestSeriatimSubprocessHelper -- \"$@\"\n"
|
|
if err := os.WriteFile(path, []byte(content), 0o755); err != nil {
|
|
t.Fatalf("WriteFile(%q) error = %v", path, err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
func mergeReqForTest(t *testing.T, withReport bool) MergeRequest {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
in1 := filepath.Join(dir, "a.json")
|
|
in2 := filepath.Join(dir, "b.json")
|
|
writeSeriatimFile(t, in1, `{"a":1}`)
|
|
writeSeriatimFile(t, in2, `{"b":1}`)
|
|
req := MergeRequest{
|
|
GeneratedConfigPath: filepath.Join(dir, "seriatim.generated.yml"),
|
|
InputTranscriptPaths: []string{in1, in2},
|
|
OutputMergedTranscriptPath: filepath.Join(dir, "base.json"),
|
|
StdoutLogPath: filepath.Join(dir, "seriatim.stdout.log"),
|
|
StderrLogPath: filepath.Join(dir, "seriatim.stderr.log"),
|
|
}
|
|
if withReport {
|
|
req.ReportPath = filepath.Join(dir, "seriatim.report.json")
|
|
}
|
|
return req
|
|
}
|
|
|
|
func trimReqForTest(t *testing.T) TrimRequest {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
input := filepath.Join(dir, "polished.json")
|
|
writeSeriatimFile(t, input, `{"schema":"seriatim.intermediate.v1","segments":[]}`)
|
|
return TrimRequest{
|
|
InputTranscriptPath: input,
|
|
OutputTrimmedPath: filepath.Join(dir, "final.trimmed.json"),
|
|
KeepSelector: "5-12",
|
|
GeneratedConfigPath: filepath.Join(dir, "seriatim.trim.generated.yml"),
|
|
StdoutLogPath: filepath.Join(dir, "seriatim.trim.stdout.log"),
|
|
StderrLogPath: filepath.Join(dir, "seriatim.trim.stderr.log"),
|
|
}
|
|
}
|
|
|
|
func normalizeReqForTest(t *testing.T, withReport bool) NormalizeRequest {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
input := filepath.Join(dir, "polished.json")
|
|
writeSeriatimFile(t, input, `{"schema":"audita.processed.v1","segments":[]}`)
|
|
|
|
req := NormalizeRequest{
|
|
InputTranscriptPath: input,
|
|
OutputNormalizedPath: filepath.Join(dir, "final.json"),
|
|
OutputSchema: "seriatim-intermediate",
|
|
GeneratedConfigPath: filepath.Join(dir, "seriatim.normalize.generated.yml"),
|
|
StdoutLogPath: filepath.Join(dir, "seriatim.normalize.stdout.log"),
|
|
StderrLogPath: filepath.Join(dir, "seriatim.normalize.stderr.log"),
|
|
}
|
|
if withReport {
|
|
req.ReportPath = filepath.Join(dir, "seriatim.normalize.report.json")
|
|
}
|
|
return req
|
|
}
|
|
|
|
func renderReqForTest(t *testing.T) RenderRequest {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
input := filepath.Join(dir, "final.trimmed.json")
|
|
writeSeriatimFile(t, input, `{"schema":"seriatim.intermediate.v1","segments":[]}`)
|
|
return RenderRequest{
|
|
InputTranscriptPath: input,
|
|
OutputRenderedPath: filepath.Join(dir, "final.trimmed.md"),
|
|
Format: "markdown",
|
|
Title: "Session 42",
|
|
IncludeTimestamps: true,
|
|
IncludeSegmentIDs: true,
|
|
IncludeMetadata: false,
|
|
GeneratedConfigPath: filepath.Join(dir, "seriatim.render.generated.yml"),
|
|
StdoutLogPath: filepath.Join(dir, "seriatim.render.stdout.log"),
|
|
StderrLogPath: filepath.Join(dir, "seriatim.render.stderr.log"),
|
|
}
|
|
}
|
|
|
|
func mustRunner(t *testing.T, binary string, report bool) *SubprocessRunner {
|
|
t.Helper()
|
|
coalesce := 3.0
|
|
r, err := NewSubprocessRunner(SubprocessRunnerConfig{
|
|
Binary: binary,
|
|
Timeout: mustParseDuration(t, "2s"),
|
|
OutputSchema: "seriatim-intermediate",
|
|
CoalesceGap: &coalesce,
|
|
Report: report,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSubprocessRunner() error = %v", err)
|
|
}
|
|
return r
|
|
}
|
|
|
|
func mustParseDuration(t *testing.T, v string) time.Duration {
|
|
t.Helper()
|
|
d, err := time.ParseDuration(v)
|
|
if err != nil {
|
|
t.Fatalf("time.ParseDuration(%q) error = %v", v, err)
|
|
}
|
|
return d
|
|
}
|
|
|
|
func flagValue(args []string, name string) string {
|
|
for i := 0; i < len(args)-1; i++ {
|
|
if args[i] == name {
|
|
return args[i+1]
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func writeSeriatimFile(t *testing.T, path, contents string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
t.Fatalf("MkdirAll(%q): %v", path, err)
|
|
}
|
|
if err := os.WriteFile(path, []byte(contents), 0o644); err != nil {
|
|
t.Fatalf("WriteFile(%q): %v", path, err)
|
|
}
|
|
}
|
|
|
|
func writeSeriatimHelperFile(path, contents string) {
|
|
if strings.TrimSpace(path) == "" {
|
|
return
|
|
}
|
|
_ = os.MkdirAll(filepath.Dir(path), 0o755)
|
|
_ = os.WriteFile(path, []byte(contents), 0o644)
|
|
}
|
|
|
|
func assertJSONFile(t *testing.T, path string) {
|
|
t.Helper()
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("ReadFile(%q): %v", path, err)
|
|
}
|
|
var v any
|
|
if err := json.Unmarshal(data, &v); err != nil {
|
|
t.Fatalf("json unmarshal %q: %v", path, err)
|
|
}
|
|
}
|
|
|
|
func readHelperRecord(t *testing.T, path string) helperRecord {
|
|
t.Helper()
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("ReadFile(%q): %v", path, err)
|
|
}
|
|
var rec helperRecord
|
|
if err := json.Unmarshal(data, &rec); err != nil {
|
|
t.Fatalf("json unmarshal helper record: %v", err)
|
|
}
|
|
return rec
|
|
}
|