diff --git a/tests/e2e/README.md b/tests/e2e/README.md index cd5b8a9..547e982 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -9,8 +9,11 @@ coverage instrumentation and runs it as a real subprocess: - `-format json` — exit 0, nothing on stderr, every stdout line decodes into `reporter.ProgressEvent` with unknown fields rejected, every `type` is a - known `EventType`, every `timestamp` is RFC3339, and the last event is - `complete`; + known `EventType`, every `timestamp` is RFC3339, the last event is + `complete`, and the full decoded event-type sequence matches an exact + sequence derived from the example's fixed fixture data — a missing, extra, + or reordered intermediate `step`/`progress`/`message`/`warning`/`error` + event fails the run instead of passing on shape and terminal event alone; - `-format text` — exit 0, non-empty output whose first line is not JSON; - `-format noop` — exit 0, nothing on stderr, no JSON event lines, and strictly less output than text mode (the reporter's contribution is gone; diff --git a/tests/e2e/examples_test.go b/tests/e2e/examples_test.go index 101d084..1b49afc 100644 --- a/tests/e2e/examples_test.go +++ b/tests/e2e/examples_test.go @@ -177,6 +177,13 @@ func TestExamples(t *testing.T) { if last := events[len(events)-1]; last.Type != reporter.EventTypeComplete { t.Errorf("last event type = %q, want %q", last.Type, reporter.EventTypeComplete) } + got := make([]reporter.EventType, len(events)) + for i, ev := range events { + got[i] = ev.Type + } + if want := expectedEventSequence(t, name); !reflect.DeepEqual(got, want) { + t.Errorf("event type sequence = %v, want %v", got, want) + } }) t.Run("text", func(t *testing.T) { @@ -357,6 +364,67 @@ func TestDecodeEventLine(t *testing.T) { } } +// expectedEventSequence returns the exact JSON EventType sequence a runnable +// example must emit, derived from its fixed fixture data in +// _examples//main.go. A missing, extra, or reordered intermediate event +// (Step/Progress/Message/Warning/Error) fails the calling test even when the +// stream still ends on a complete event. +func expectedEventSequence(t *testing.T, name string) []reporter.EventType { + t.Helper() + step, progress, message, warning, errType, complete := + reporter.EventTypeStep, reporter.EventTypeProgress, reporter.EventTypeMessage, + reporter.EventTypeWarning, reporter.EventTypeError, reporter.EventTypeComplete + + switch name { + case "deploy": + // Step 1: Message, Warning. Step 2: Message. Step 3: Message, Warning. + // Step 4: Message. Then Complete. + return []reporter.EventType{ + step, message, warning, + step, message, + step, message, warning, + step, message, + complete, + } + case "fileprocess": + // document.pdf, photo.jpg, and report.docx are processed (Message, + // Progress); archive.tar.gz and notes.txt are skipped (Message only). + return []reporter.EventType{ + step, message, progress, + step, message, progress, + step, message, + step, message, progress, + step, message, + complete, + } + case "healthcheck": + // database and cache are healthy (Message); API gateway is degraded + // (Warning); message queue is down (Error). MessagePlain before + // Complete decodes as a message event. + return []reporter.EventType{ + step, message, + step, message, + step, warning, + step, errType, + message, + complete, + } + case "migration": + // Phase 1: Message. Phase 2: five batches of Progress, with a + // Warning after the third batch's Progress. Phase 3: Message. + return []reporter.EventType{ + step, message, + step, + progress, progress, progress, warning, progress, progress, + step, message, + complete, + } + default: + t.Fatalf("expectedEventSequence: no fixed event sequence known for example %q", name) + return nil + } +} + func validEventType(et reporter.EventType) bool { switch et { case reporter.EventTypeStep, reporter.EventTypeProgress, reporter.EventTypeMessage,