Documentation
¶
Overview ¶
Package conformance captures the observable error behaviour of Cloud Tasks (real or emulated) by firing a fixed battery of deliberately-malformed and edge-case RPCs at a target and recording the resulting gRPC status.
The same battery runs against the real API (to produce a committed golden snapshot) and against the emulator (to validate it). Because Cloud Tasks interpolates request values into some error messages, results are normalized into templates with placeholders before comparison - see normalize.go.
Index ¶
- Variables
- func LoadDispatch(path string) (map[string]DispatchSnapshot, error)
- func LoadErrors(path string) (map[string]CaseResult, error)
- func LoadHappyPath(path string) (map[string]HappyPathSnapshot, error)
- func NewEmulatorClient(ctx context.Context, addr string) (*cloudtasks.Client, error)
- func NewRealClient(ctx context.Context) (*cloudtasks.Client, error)
- func Normalize(msg string, p Params) string
- func SaveDispatch(path string, snaps []DispatchSnapshot) error
- func SaveErrors(path string, results []CaseResult) error
- func SaveHappyPath(path string, snaps []HappyPathSnapshot) error
- type Captured
- type Case
- type CaseResult
- type Client
- type DetailRecord
- type Diff
- type DispatchAttempt
- type DispatchSnapshot
- type HappyPathSnapshot
- type Params
- type PerturbationReport
- type Result
- type RunOptions
- type SizeProbeOptions
- type TargetSizeReport
Constants ¶
This section is empty.
Variables ¶
var KnownDivergences = map[string]string{
"queue/create/invalid-parent": "real resolves any parent string to a project and returns PermissionDenied via IAM; the emulator has no project/IAM concept and returns InvalidArgument. Not reproducible by design.",
}
KnownDivergences are cases where the emulator is currently expected to differ from real Cloud Tasks for reasons beyond error-message mapping - genuine behaviour gaps we have chosen to defer. The validation test reports these as KNOWN rather than failing on them, and flags any that have started matching (so the entry can be removed once the gap is closed).
Keep this list short and each entry justified; it is the explicit ledger of "the emulator is not faithful here, on purpose, for now".
Functions ¶
func LoadDispatch ¶
func LoadDispatch(path string) (map[string]DispatchSnapshot, error)
LoadDispatch reads a dispatch golden keyed by case name.
func LoadErrors ¶
func LoadErrors(path string) (map[string]CaseResult, error)
LoadErrors reads an error-battery golden keyed by case name.
func LoadHappyPath ¶
func LoadHappyPath(path string) (map[string]HappyPathSnapshot, error)
LoadHappyPath reads a happy-path golden keyed by observation name.
func NewEmulatorClient ¶
NewEmulatorClient points the same official client at a running emulator over an insecure local channel, with no credentials.
func NewRealClient ¶
func NewRealClient(ctx context.Context) (*cloudtasks.Client, error)
NewRealClient connects to the production Cloud Tasks API using Application Default Credentials (run `gcloud auth application-default login` first). The official client sets the routing headers and per-method deadlines itself, so what we record is exactly what a real caller using the SDK would see.
func Normalize ¶
Normalize replaces request-specific substrings in a message with stable placeholders, so messages captured with different inputs compare equal. The derived "template" is what we store and diff. Replacements run most-specific first so longer resource paths are matched before their components.
Anything that survives normalization is treated as static text. By running a case with several differing inputs and checking the templates agree (see CaseResult.Stable), we confirm we have correctly identified every interpolated slot - any input value we failed to placeholder would leak through and make the variants disagree.
func SaveDispatch ¶
func SaveDispatch(path string, snaps []DispatchSnapshot) error
SaveDispatch writes dispatch snapshots to path (see saveGolden).
func SaveErrors ¶
func SaveErrors(path string, results []CaseResult) error
SaveErrors writes error-battery results to path (see saveGolden). Per-variant detail is dropped from the committed golden - the aggregate is the contract; variants are kept only in ad-hoc dumps if a caller wants them.
func SaveHappyPath ¶
func SaveHappyPath(path string, snaps []HappyPathSnapshot) error
SaveHappyPath writes happy-path snapshots to path (see saveGolden).
Types ¶
type Captured ¶
type Captured struct {
Headers map[string]string `json:"headers,omitempty"`
Body []byte `json:"body,omitempty"`
Err string `json:"err,omitempty"`
}
Captured is what one read observed - the task's headers and body - or the error that read returned. Headers and Body are nil when Err is set.
type Case ¶
type Case struct {
Name string
RPC string
Category string
Setup func(ctx context.Context, c *Client, p Params) error
Invoke func(ctx context.Context, c *Client, p Params) error
Teardown func(ctx context.Context, c *Client, p Params) error
}
Case is one error scenario exercised against a target. Setup establishes any precondition (e.g. create-then-delete to reach "recently deleted"); Invoke performs the RPC whose error we want to capture; Teardown is best-effort cleanup. Setup/Teardown may be nil.
Each case is run with several Params variants so the recorder can distinguish static message text from interpolated request values.
type CaseResult ¶
type CaseResult struct {
Name string `json:"name"`
RPC string `json:"rpc"`
Category string `json:"category"`
Code string `json:"code"`
Template string `json:"template"`
Details []DetailRecord `json:"details,omitempty"`
Stable bool `json:"stable"`
Variants []Result `json:"variants,omitempty"`
}
CaseResult aggregates a case's variants into the canonical record that goes into the golden file. Stable is true when every variant agreed on code and template after normalization; when false the variants diverged and the template should not be trusted as-is.
func RunErrors ¶
func RunErrors(ctx context.Context, c *Client, opts RunOptions) []CaseResult
RunErrors executes the error battery against the client and returns one CaseResult per case. It never aborts on an individual RPC failure - failures are the data being collected.
type Client ¶
type Client = cloudtasks.Client
Client is the official Cloud Tasks client; the same type drives both the real API and the emulator.
type DetailRecord ¶
DetailRecord is one entry from a gRPC status' details, with its message text normalized the same way as the top-level message.
type Diff ¶
Diff describes one mismatch between a recorded result and the golden.
func CompareDispatch ¶
func CompareDispatch(golden map[string]DispatchSnapshot, got []DispatchSnapshot) []Diff
CompareDispatch checks recorded dispatch snapshots against a golden, returning a Diff per case whose formatted attempts differ.
func CompareErrors ¶
func CompareErrors(golden map[string]CaseResult, got []CaseResult) []Diff
CompareErrors checks recorded error-battery results against a golden, returning a Diff per mismatched code, template or details.
func CompareHappyPath ¶
func CompareHappyPath(golden map[string]HappyPathSnapshot, got []HappyPathSnapshot) []Diff
CompareHappyPath checks recorded snapshots against a golden, returning a Diff per mismatched read stage (headers or body).
type DispatchAttempt ¶
type DispatchAttempt struct {
Attempt int `json:"attempt"` // X-*-TaskRetryCount (0 on first delivery)
Status int `json:"status"` // status the receiver returned for this attempt
Headers map[string]string `json:"headers"` // dispatch headers, filtered + normalized
}
DispatchAttempt is what the receiver observed for one delivery attempt of a dispatched task.
type DispatchSnapshot ¶
type DispatchSnapshot struct {
Name string `json:"name"` // stable golden key, e.g. "dispatch/http"
RequestType string `json:"requestType"` // "http" | "appengine" | "http-timeout"
Attempts []DispatchAttempt `json:"attempts"` // sorted by Attempt ascending
}
DispatchSnapshot is one case's golden entry: every delivery attempt the receiver observed for that case's task, in attempt order. A healthy standard case runs through the whole forced-status sequence before a 200 (six attempts); the timeout case has two (a timed-out attempt, then a 200). Fewer than expected means a retry did not happen (or the task never dispatched) - that gap is itself meaningful and is surfaced by the record command rather than silently swallowed.
func RunDispatch ¶
func RunDispatch(ctx context.Context, c *Client, opts RunOptions, receiverURL string) []DispatchSnapshot
RunDispatch executes the dispatch battery against the client and returns one snapshot per case. receiverURL is the base URL of a running receiver (see test/conformance/receiver) - either a deployed App Engine app (to record the golden from real Cloud Tasks) or a local server (for hermetic emulator validation). Like the other batteries, a failure at any stage is recorded as data (an empty or short Attempts slice) rather than aborting the run.
type HappyPathSnapshot ¶
type HappyPathSnapshot struct {
Name string `json:"name"`
RequestType string `json:"requestType"` // http | appengine
Sent map[string]string `json:"sent"`
CreateFull Captured `json:"createFull"` // CreateTask response, FULL view
GetBasic Captured `json:"getBasic"` // GetTask, BASIC view
GetFull Captured `json:"getFull"` // GetTask, FULL view
}
HappyPathSnapshot is one observation's golden entry: the headers we submitted alongside what Cloud Tasks echoed back at each read stage.
func RunHappyPath ¶
func RunHappyPath(ctx context.Context, c *Client, opts RunOptions) []HappyPathSnapshot
RunHappyPath executes the happy-path battery against the client and returns one snapshot per observation. Like Run, it never aborts on an individual RPC failure - a failure is recorded in the relevant Captured.Err (e.g. a FULL-view read without cloudtasks.tasks.fullView) and is itself data.
type Params ¶
Params are the request-shaping values for one invocation of a case. The runner generates several variants per case with differing QueueID/TaskID so normalize.go can tell static message text from interpolated request values.
func (Params) Parent ¶
Parent is the location resource name (CreateQueue parent, ListQueues parent).
type PerturbationReport ¶
type PerturbationReport struct {
Name string `json:"name"`
// ProtoDelta is how much the perturbation grows the serialized Task proto.
ProtoDelta int `json:"protoDelta"`
// PredictedMaxBody is baseline MaxBody - ProtoDelta: where the boundary
// lands if the perturbed field counts exactly its proto encoding.
PredictedMaxBody int `json:"predictedMaxBody"`
AtPredicted string `json:"atPredicted"` // outcome at PredictedMaxBody
OverPredicted string `json:"overPredicted"` // outcome at PredictedMaxBody+1
// Consistent means accepted at the predicted max and size-rejected one
// over: the field's weight matches its proto encoding exactly.
Consistent bool `json:"consistentWithProtoSize"`
// ActualMaxBody is measured by a full binary search when the prediction
// failed; -1 when the prediction held (or the search was inconclusive).
ActualMaxBody int `json:"actualMaxBody"`
Note string `json:"note,omitempty"`
}
PerturbationReport records one field's measured contribution to task size.
type Result ¶
type Result struct {
Params Params `json:"params"`
SetupErr string `json:"setupErr,omitempty"`
Code string `json:"code"`
Message string `json:"message"`
Template string `json:"template"`
Details []DetailRecord `json:"details,omitempty"`
}
Result is the captured outcome of a single (case, variant) invocation.
type RunOptions ¶
type RunOptions struct {
Project string
Location string
Prefix string // run-scoped resource-name prefix, keeps re-runs from colliding
Variants int // number of differing-input variants per case (>=2 to detect templates)
}
RunOptions configure a recording run.
type SizeProbeOptions ¶
type SizeProbeOptions struct {
Project string
Location string
Prefix string // run-scoped resource-name prefix, keeps re-runs from colliding
Targets []string // subset of {"http", "appengine"}; empty probes both
Log io.Writer // per-attempt progress log; nil discards
}
SizeProbeOptions configure a size-probe run.
type TargetSizeReport ¶
type TargetSizeReport struct {
Target string `json:"target"`
Err string `json:"err,omitempty"` // set when the probe aborted for this target
// MaxBody is the largest accepted body length on the baseline task.
MaxBody int `json:"maxBody"`
// Serialized proto sizes of the baseline task at the MaxBody boundary,
// for matching the measured limit against candidate formulas.
TaskProtoSizeAtMax int `json:"taskProtoSizeAtMax"`
RequestProtoSizeAtMax int `json:"requestProtoSizeAtMax"`
MessageProtoSizeAtMax int `json:"messageProtoSizeAtMax"` // the HttpRequest / AppEngineHttpRequest submessage
// The first over-limit rejection observed, verbatim - this is what the
// emulator's error mapping should reproduce.
RejectCode string `json:"rejectCode"`
RejectMessage string `json:"rejectMessage"`
RejectDetails []DetailRecord `json:"rejectDetails,omitempty"`
// OtherRejections lists any rejection whose digit-stripped template
// differs from the first - non-empty means not every rejection during the
// search was the same (size) error, so inspect before trusting MaxBody.
OtherRejections []string `json:"otherRejections,omitempty"`
Perturbations []PerturbationReport `json:"perturbations,omitempty"`
Calls int `json:"calls"` // CreateTask calls spent on this target
}
TargetSizeReport is what the probe learned about one target type.
func RunSizeProbe ¶
func RunSizeProbe(ctx context.Context, c *Client, opts SizeProbeOptions) []TargetSizeReport
RunSizeProbe probes each requested target type. Per-target failures land in the report's Err field rather than aborting the run, so one target's environment problem (e.g. no App Engine app in the project) doesn't cost the other's results; only context cancellation stops the whole run.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
probe
command
Command probe adaptively discovers how Cloud Tasks measures the task size limit: the exact boundary per target type, which fields count toward it, and the exact rejection error.
|
Command probe adaptively discovers how Cloud Tasks measures the task size limit: the exact boundary per target type, which fields count toward it, and the exact rejection error. |
|
record
command
Command record fires the conformance battery at a target (real Cloud Tasks or a running emulator) and writes the captured results as JSON.
|
Command record fires the conformance battery at a target (real Cloud Tasks or a running emulator) and writes the captured results as JSON. |
|
receiver
module
|