| OLD | NEW |
| 1 // Copyright 2015 The LUCI Authors. All rights reserved. | 1 // Copyright 2015 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package annotation | 5 package annotation |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "bufio" | 8 "bufio" |
| 9 "flag" | 9 "flag" |
| 10 "fmt" | 10 "fmt" |
| (...skipping 21 matching lines...) Expand all Loading... |
| 32 | 32 |
| 33 var generate = flag.Bool("annotee.generate", false, "If true, regenerate expecta
tions from source.") | 33 var generate = flag.Bool("annotee.generate", false, "If true, regenerate expecta
tions from source.") |
| 34 | 34 |
| 35 type testCase struct { | 35 type testCase struct { |
| 36 name string | 36 name string |
| 37 exe *Execution | 37 exe *Execution |
| 38 } | 38 } |
| 39 | 39 |
| 40 func (tc *testCase) state(startTime time.Time) *State { | 40 func (tc *testCase) state(startTime time.Time) *State { |
| 41 cb := testCallbacks{ | 41 cb := testCallbacks{ |
| 42 » » closed: map[string]struct{}{}, | 42 » » closed: map[*Step]struct{}{}, |
| 43 logs: map[types.StreamName][]string{}, | 43 logs: map[types.StreamName][]string{}, |
| 44 logsOpen: map[types.StreamName]struct{}{}, | 44 logsOpen: map[types.StreamName]struct{}{}, |
| 45 } | 45 } |
| 46 return &State{ | 46 return &State{ |
| 47 LogNameBase: types.StreamName("base"), | 47 LogNameBase: types.StreamName("base"), |
| 48 Callbacks: &cb, | 48 Callbacks: &cb, |
| 49 Clock: testclock.New(startTime), | 49 Clock: testclock.New(startTime), |
| 50 Execution: tc.exe, | 50 Execution: tc.exe, |
| 51 } | 51 } |
| 52 } | 52 } |
| 53 | 53 |
| 54 func (tc *testCase) generate(t *testing.T, startTime time.Time, touched stringse
t.Set) error { | 54 func (tc *testCase) generate(t *testing.T, startTime time.Time, touched stringse
t.Set) error { |
| 55 st := tc.state(startTime) | 55 st := tc.state(startTime) |
| 56 p, err := playAnnotationScript(t, tc.name, st) | 56 p, err := playAnnotationScript(t, tc.name, st) |
| 57 if err != nil { | 57 if err != nil { |
| 58 return err | 58 return err |
| 59 } | 59 } |
| 60 touched.Add(p) | 60 touched.Add(p) |
| 61 st.Finish() | 61 st.Finish() |
| 62 | 62 |
| 63 // Write out generated protos. | 63 // Write out generated protos. |
| 64 merr := errors.MultiError(nil) | 64 merr := errors.MultiError(nil) |
| 65 | 65 |
| 66 step := st.RootStep() | 66 step := st.RootStep() |
| 67 » p, err = writeStepProto(tc.name, step.CanonicalName(), step.Proto()) | 67 » p, err = writeStepProto(tc.name, step) |
| 68 if err != nil { | 68 if err != nil { |
| 69 » » merr = append(merr, fmt.Errorf("Failed to write step proto for %
q::%q: %v", tc.name, step.CanonicalName(), err)) | 69 » » merr = append(merr, fmt.Errorf("Failed to write step proto for %
q::%q: %v", tc.name, step.LogNameBase, err)) |
| 70 } | 70 } |
| 71 touched.Add(p) | 71 touched.Add(p) |
| 72 | 72 |
| 73 // Write out generated logs. | 73 // Write out generated logs. |
| 74 cb := st.Callbacks.(*testCallbacks) | 74 cb := st.Callbacks.(*testCallbacks) |
| 75 for logName, lines := range cb.logs { | 75 for logName, lines := range cb.logs { |
| 76 p, err := writeLogText(tc.name, string(logName), lines) | 76 p, err := writeLogText(tc.name, string(logName), lines) |
| 77 if err != nil { | 77 if err != nil { |
| 78 merr = append(merr, fmt.Errorf("Failed to write log text
for %q::%q: %v", tc.name, logName, err)) | 78 merr = append(merr, fmt.Errorf("Failed to write log text
for %q::%q: %v", tc.name, logName, err)) |
| 79 } | 79 } |
| (...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 164 } | 164 } |
| 165 } else if err != nil { | 165 } else if err != nil { |
| 166 return "", err | 166 return "", err |
| 167 } | 167 } |
| 168 } | 168 } |
| 169 } | 169 } |
| 170 | 170 |
| 171 return path, nil | 171 return path, nil |
| 172 } | 172 } |
| 173 | 173 |
| 174 func loadStepProto(t *testing.T, test, name string) *milo.Step { | 174 func loadStepProto(t *testing.T, test string, s *Step) *milo.Step { |
| 175 » path := filepath.Join(testExpDir, fmt.Sprintf("%s_%s.proto.txt", normali
ze(test), normalize(name))) | 175 » path := filepath.Join(testExpDir, fmt.Sprintf("%s_%s.proto.txt", normali
ze(test), normalize(string(s.LogNameBase)))) |
| 176 data, err := ioutil.ReadFile(path) | 176 data, err := ioutil.ReadFile(path) |
| 177 if err != nil { | 177 if err != nil { |
| 178 t.Errorf("Failed to read milo.Step proto [%s]: %v", path, err) | 178 t.Errorf("Failed to read milo.Step proto [%s]: %v", path, err) |
| 179 return nil | 179 return nil |
| 180 } | 180 } |
| 181 | 181 |
| 182 st := milo.Step{} | 182 st := milo.Step{} |
| 183 if err := proto.UnmarshalText(string(data), &st); err != nil { | 183 if err := proto.UnmarshalText(string(data), &st); err != nil { |
| 184 t.Errorf("Failed to Unmarshal milo.Step proto [%s]: %v", path, e
rr) | 184 t.Errorf("Failed to Unmarshal milo.Step proto [%s]: %v", path, e
rr) |
| 185 return nil | 185 return nil |
| 186 } | 186 } |
| 187 return &st | 187 return &st |
| 188 } | 188 } |
| 189 | 189 |
| 190 func writeStepProto(test, name string, step *milo.Step) (string, error) { | 190 func writeStepProto(test string, s *Step) (string, error) { |
| 191 » path := filepath.Join(testExpDir, fmt.Sprintf("%s_%s.proto.txt", normali
ze(test), normalize(name))) | 191 » path := filepath.Join(testExpDir, fmt.Sprintf("%s_%s.proto.txt", normali
ze(test), normalize(string(s.LogNameBase)))) |
| 192 » return path, ioutil.WriteFile(path, []byte(proto.MarshalTextString(step)
), 0644) | 192 » return path, ioutil.WriteFile(path, []byte(proto.MarshalTextString(s.Pro
to())), 0644) |
| 193 } | 193 } |
| 194 | 194 |
| 195 func loadLogText(t *testing.T, test, name string) []string { | 195 func loadLogText(t *testing.T, test, name string) []string { |
| 196 path := filepath.Join(testExpDir, fmt.Sprintf("%s_%s.txt", normalize(tes
t), normalize(name))) | 196 path := filepath.Join(testExpDir, fmt.Sprintf("%s_%s.txt", normalize(tes
t), normalize(name))) |
| 197 f, err := os.Open(path) | 197 f, err := os.Open(path) |
| 198 if err != nil { | 198 if err != nil { |
| 199 t.Errorf("Failed to open log lines [%s]: %v", path, err) | 199 t.Errorf("Failed to open log lines [%s]: %v", path, err) |
| 200 return nil | 200 return nil |
| 201 } | 201 } |
| 202 defer f.Close() | 202 defer f.Close() |
| 203 | 203 |
| 204 lines := []string(nil) | 204 lines := []string(nil) |
| 205 scanner := bufio.NewScanner(f) | 205 scanner := bufio.NewScanner(f) |
| 206 for scanner.Scan() { | 206 for scanner.Scan() { |
| 207 lines = append(lines, scanner.Text()) | 207 lines = append(lines, scanner.Text()) |
| 208 } | 208 } |
| 209 return lines | 209 return lines |
| 210 } | 210 } |
| 211 | 211 |
| 212 func writeLogText(test, name string, text []string) (string, error) { | 212 func writeLogText(test, name string, text []string) (string, error) { |
| 213 path := filepath.Join(testExpDir, fmt.Sprintf("%s_%s.txt", normalize(tes
t), normalize(name))) | 213 path := filepath.Join(testExpDir, fmt.Sprintf("%s_%s.txt", normalize(tes
t), normalize(name))) |
| 214 return path, ioutil.WriteFile(path, []byte(strings.Join(text, "\n")), 06
44) | 214 return path, ioutil.WriteFile(path, []byte(strings.Join(text, "\n")), 06
44) |
| 215 } | 215 } |
| 216 | 216 |
| 217 // testCallbacks implements the Callbacks interface, retaining all callback | 217 // testCallbacks implements the Callbacks interface, retaining all callback |
| 218 // data in memory. | 218 // data in memory. |
| 219 type testCallbacks struct { | 219 type testCallbacks struct { |
| 220 » // closed is the set of steps that have been closed, keyed on step | 220 » // closed is the set of steps that have been closed. |
| 221 » // CanonicalName. | 221 » closed map[*Step]struct{} |
| 222 » closed map[string]struct{} | |
| 223 | 222 |
| 224 // logs is the content of emitted annotation logs, keyed on stream name. | 223 // logs is the content of emitted annotation logs, keyed on stream name. |
| 225 logs map[types.StreamName][]string | 224 logs map[types.StreamName][]string |
| 226 // logsOpen tracks whether a given annotation log is open. | 225 // logsOpen tracks whether a given annotation log is open. |
| 227 logsOpen map[types.StreamName]struct{} | 226 logsOpen map[types.StreamName]struct{} |
| 228 } | 227 } |
| 229 | 228 |
| 230 func (tc *testCallbacks) StepClosed(s *Step) { | 229 func (tc *testCallbacks) StepClosed(s *Step) { |
| 231 » tc.closed[s.CanonicalName()] = struct{}{} | 230 » tc.closed[s] = struct{}{} |
| 232 } | 231 } |
| 233 | 232 |
| 234 func (tc *testCallbacks) StepLogLine(s *Step, n types.StreamName, label, line st
ring) { | 233 func (tc *testCallbacks) StepLogLine(s *Step, n types.StreamName, label, line st
ring) { |
| 235 if _, ok := tc.logs[n]; ok { | 234 if _, ok := tc.logs[n]; ok { |
| 236 // The log exists. Assert that it is open. | 235 // The log exists. Assert that it is open. |
| 237 if _, ok := tc.logsOpen[n]; !ok { | 236 if _, ok := tc.logsOpen[n]; !ok { |
| 238 panic(fmt.Errorf("write to closed log stream: %q", n)) | 237 panic(fmt.Errorf("write to closed log stream: %q", n)) |
| 239 } | 238 } |
| 240 } | 239 } |
| 241 | 240 |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 310 // All log streams should be closed. | 309 // All log streams should be closed. |
| 311 cb := st.Callbacks.(*testCallbacks) | 310 cb := st.Callbacks.(*testCallbacks) |
| 312 So(cb.logsOpen, ShouldResemble, map[types.Stream
Name]struct{}{}) | 311 So(cb.logsOpen, ShouldResemble, map[types.Stream
Name]struct{}{}) |
| 313 | 312 |
| 314 // Iterate over each generated stream and assert
that it matches its | 313 // Iterate over each generated stream and assert
that it matches its |
| 315 // expectation. Do it deterministically so failu
res aren't frustrating | 314 // expectation. Do it deterministically so failu
res aren't frustrating |
| 316 // to reproduce. | 315 // to reproduce. |
| 317 Convey(`Has correct Step value`, func() { | 316 Convey(`Has correct Step value`, func() { |
| 318 rootStep := st.RootStep() | 317 rootStep := st.RootStep() |
| 319 | 318 |
| 320 » » » » » exp := loadStepProto(t, testCase.name, r
ootStep.CanonicalName()) | 319 » » » » » exp := loadStepProto(t, testCase.name, r
ootStep) |
| 321 So(rootStep.Proto(), ShouldResemble, exp
) | 320 So(rootStep.Proto(), ShouldResemble, exp
) |
| 322 }) | 321 }) |
| 323 | 322 |
| 324 // Iterate over each generated log and assert th
at it matches its | 323 // Iterate over each generated log and assert th
at it matches its |
| 325 // expectations. | 324 // expectations. |
| 326 logs := make([]string, 0, len(cb.logs)) | 325 logs := make([]string, 0, len(cb.logs)) |
| 327 for k := range cb.logs { | 326 for k := range cb.logs { |
| 328 logs = append(logs, string(k)) | 327 logs = append(logs, string(k)) |
| 329 } | 328 } |
| 330 sort.Strings(logs) | 329 sort.Strings(logs) |
| 331 for _, logName := range logs { | 330 for _, logName := range logs { |
| 332 log := cb.logs[types.StreamName(logName)
] | 331 log := cb.logs[types.StreamName(logName)
] |
| 333 exp := loadLogText(t, testCase.name, log
Name) | 332 exp := loadLogText(t, testCase.name, log
Name) |
| 334 So(log, ShouldResemble, exp) | 333 So(log, ShouldResemble, exp) |
| 335 } | 334 } |
| 336 }) | 335 }) |
| 337 } | 336 } |
| 338 | 337 |
| 339 Convey(`Append to a closed State is a no-op.`, func() { | 338 Convey(`Append to a closed State is a no-op.`, func() { |
| 340 st := testCases[0].state(startTime) | 339 st := testCases[0].state(startTime) |
| 341 st.Finish() | 340 st.Finish() |
| 342 sclone := st | 341 sclone := st |
| 343 So(st.Append("asdf"), ShouldBeNil) | 342 So(st.Append("asdf"), ShouldBeNil) |
| 344 So(st, ShouldResemble, sclone) | 343 So(st, ShouldResemble, sclone) |
| 345 }) | 344 }) |
| 346 }) | 345 }) |
| 347 } | 346 } |
| OLD | NEW |