pkg/models/set_shares.go
Rama main · réplica de lectura del repositorio original.
package models
import (
"sort"
"strings"
"time"
)
// SetShares is what keploy record's full round settled, at a set's end, of the
// recordings it shared between tests whose requests were open together while
// they were recorded (Mapping.Groups, ExactServing.Concurrent): which test
// made each call. Under the proven switch only, and only in that round; the
// pin step of keploy record's judging writes it onto mappings.yaml
// (Mapping.ApplyShares) before the confirming round replays it.
type SetShares struct {
// State says whether the round shared recordings in the set, and whether
// what it settled can be written.
State SharesState `json:"state" yaml:"state"`
// Why is, for SharesIncomplete, why the set's shares cannot be settled.
Why string `json:"why,omitempty" yaml:"why,omitempty"`
// Pins gives each shared recording a test made the call of to that test,
// by mock name: it is that test's from here, and in no group.
Pins map[string]string `json:"pins,omitempty" yaml:"pins,omitempty"`
// Moves are the shared recordings keploy could not give to the test that
// made their call, by mock name: each goes to a test the round charged
// for it (ShareMove.To), marked when its recordings cannot be told apart
// (ShareMove.Marker), so that no test the round did not charge is left
// with a call it did not make.
Moves map[string]ShareMove `json:"moves,omitempty" yaml:"moves,omitempty"`
}
// SharesState is SetShares.State.
type SharesState string
const (
// SharesNone: no test of the set was served a recording it shares
// with another: its mappings.yaml stays as recorded.
SharesNone SharesState = ""
// SharesIncomplete: the set's run did not complete; it ended with no
// verdict, neither PASSED nor FAILED (its agent started again mid-set
// among the causes: the set then ends INTERNAL_ERR, the claims of the
// agent before gone with it); what a test that was served shared
// recordings consumed could not be read; or a shared recording could
// not be given to a test that made it or that the round charged. Its
// round's verdicts were computed with sharing, so the set is not judged,
// and its mappings.yaml stays as recorded.
SharesIncomplete SharesState = "incomplete"
// SharesComplete: every shared recording a test lists is pinned or
// moved; one Mapping.Shared lists is pinned, or stays shared.
SharesComplete SharesState = "complete"
)
// ShareMove is where a shared recording goes that keploy could not give to the
// test that made its call (SetShares.Moves).
type ShareMove struct {
To string `json:"to" yaml:"to"`
// Marker is MockEntry.Unresolved for it, "" for none.
Marker string `json:"marker,omitempty" yaml:"marker,omitempty"`
}
// Any reports whether s says anything to write.
func (s SetShares) Any() bool {
return len(s.Pins) > 0 || len(s.Moves) > 0
}
// ApplyShares writes what keploy record's full round settled (s) onto m: each
// pinned recording leaves whatever lists it (a test's entry, or Shared) and
// joins its test's entry without its group; each moved one joins its test's
// entry with its group and marker. Every list keeps recorded order, and the
// groups no entry names any more are dropped. A recording m does not list is
// left alone. It is idempotent: applied twice, it changes nothing the second
// time. It reports whether it changed m.
func (m *Mapping) ApplyShares(s SetShares) bool {
if m == nil || !s.Any() {
return false
}
before := m.sharesShape()
// A pin or a move that names no test leaves its recording where it is.
named := map[string]bool{}
for n, test := range s.Pins {
named[n] = test != ""
}
for n, mv := range s.Moves {
named[n] = named[n] || mv.To != ""
}
// Take every named entry out of where it is listed, the first listing
// kept as its entry.
taken := map[string]MockEntry{}
take := func(list []MockEntry) []MockEntry {
out := list[:0:0]
for _, e := range list {
if !named[e.Name] {
out = append(out, e)
continue
}
if _, ok := taken[e.Name]; !ok {
taken[e.Name] = e
}
}
return out
}
for i := range m.TestCases {
if kept := take(m.TestCases[i].Mocks); len(kept) != len(m.TestCases[i].Mocks) {
m.TestCases[i].Mocks = kept
}
}
if kept := take(m.Shared); len(kept) != len(m.Shared) {
m.Shared = kept
}
add := func(test string, e MockEntry) {
for i := range m.TestCases {
if m.TestCases[i].ID == test {
m.TestCases[i].Mocks = insertRecorded(m.TestCases[i].Mocks, e)
return
}
}
m.TestCases = append(m.TestCases, MappedTestCase{ID: test, Mocks: []MockEntry{e}})
}
for _, name := range sortedNames(taken) {
e := taken[name]
switch {
case s.Pins[name] != "":
e.Group, e.Unresolved = "", ""
add(s.Pins[name], e)
default:
e.Unresolved = s.Moves[name].Marker
add(s.Moves[name].To, e)
}
}
m.dropUnusedGroups()
return m.sharesShape() != before
}
// insertRecorded is list with e inserted in recorded order (by request time),
// after every entry recorded at or before it.
func insertRecorded(list []MockEntry, e MockEntry) []MockEntry {
at := entryRecordedAt(e)
i := len(list)
for i > 0 && entryRecordedAt(list[i-1]).After(at) {
i--
}
out := make([]MockEntry, 0, len(list)+1)
out = append(out, list[:i]...)
out = append(out, e)
return append(out, list[i:]...)
}
// entryRecordedAt is when an entry's request was recorded, else its
// timestamp in seconds.
func entryRecordedAt(e MockEntry) time.Time {
if at, err := ParseMockTimestamp(e.ReqTimestampMock); err == nil && !at.IsZero() {
return at
}
return time.Unix(e.Timestamp, 0)
}
// dropUnusedGroups drops the groups no entry of m names.
func (m *Mapping) dropUnusedGroups() {
if len(m.Groups) == 0 {
return
}
used := map[string]bool{}
for _, tc := range m.TestCases {
for _, e := range tc.Mocks {
if e.Group != "" {
used[e.Group] = true
}
}
}
for _, e := range m.Shared {
if e.Group != "" {
used[e.Group] = true
}
}
out := m.Groups[:0:0]
for _, g := range m.Groups {
if used[g.ID] {
out = append(out, g)
}
}
if len(out) == 0 {
out = nil
}
m.Groups = out
}
// sharesShape is what ApplyShares changes of m, as one string, to tell
// whether it changed anything.
func (m *Mapping) sharesShape() string {
var b strings.Builder
for _, tc := range m.TestCases {
b.WriteString(tc.ID)
b.WriteByte('\x00')
for _, e := range tc.Mocks {
b.WriteString(e.Name + "\x01" + e.Group + "\x01" + e.Unresolved + "\x02")
}
b.WriteByte('\x03')
}
for _, e := range m.Shared {
b.WriteString(e.Name + "\x01" + e.Group + "\x02")
}
b.WriteByte('\x03')
for _, g := range m.Groups {
b.WriteString(g.ID + "\x02")
}
return b.String()
}
// sortedNames is m's keys, sorted.
func sortedNames[V any](m map[string]V) []string {
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
sort.Strings(out)
return out
}