Merge pull request 'power: its loop and boot tests run on a fake clock, so a busy build node cannot fail them (issue 1126)' (#551) from fix/power-loop-flake into main
This commit was merged in pull request #551.
This commit is contained in:
@@ -271,13 +271,12 @@ func (m *Machine) Publish(r Reading) error {
|
||||
|
||||
// RunDraw samples and publishes until the context ends.
|
||||
func RunDraw(ctx context.Context, m *Machine) error {
|
||||
return runDraw(ctx, m, SampleEvery, m.Publish)
|
||||
return runDraw(ctx, m, SampleEvery, sleepFor(ctx), m.Publish)
|
||||
}
|
||||
|
||||
// runDraw publishes one reading every `every`. A battery reading is instant, so it waits the interval
|
||||
// itself; a counter reading waits inside its sample. It returns as soon as the context ends.
|
||||
func runDraw(ctx context.Context, m *Machine, every time.Duration, publish func(Reading) error) error {
|
||||
wait := func(d time.Duration) bool {
|
||||
// sleepFor waits d on the real clock, or less when ctx ends first; it reports whether the full wait passed.
|
||||
func sleepFor(ctx context.Context) func(time.Duration) bool {
|
||||
return func(d time.Duration) bool {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return false
|
||||
@@ -285,6 +284,12 @@ func runDraw(ctx context.Context, m *Machine, every time.Duration, publish func(
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runDraw publishes one reading every `every`. A battery reading is instant, so it waits the interval
|
||||
// itself; a counter reading waits inside its sample. The wait is injected so a test drives the clock;
|
||||
// it returns false when the loop must stop. It returns as soon as the context ends.
|
||||
func runDraw(ctx context.Context, m *Machine, every time.Duration, wait func(time.Duration) bool, publish func(Reading) error) error {
|
||||
for ctx.Err() == nil {
|
||||
d := m.Sample(ctx, every, wait)
|
||||
if ctx.Err() != nil {
|
||||
|
||||
@@ -187,24 +187,25 @@ func TestTheLoopPublishesEveryIntervalAndStopsWhenCancelled(t *testing.T) {
|
||||
"/sys/class/power_supply/BAT0/status": "Discharging",
|
||||
"/sys/class/power_supply/BAT0/power_now": "12000000",
|
||||
})
|
||||
// A fake clock: each wait returns at once; the tenth cancels the context, as the end of the run.
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
var lines []string
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
done <- runDraw(ctx, m, 20*time.Millisecond, func(r Reading) error { lines = append(lines, r.Text); return nil })
|
||||
}()
|
||||
time.Sleep(210 * time.Millisecond)
|
||||
cancel()
|
||||
select {
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
waits := 0
|
||||
wait := func(d time.Duration) bool {
|
||||
if d != 20*time.Millisecond {
|
||||
t.Errorf("waited %v, want the 20 ms interval", d)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("the loop did not stop when cancelled")
|
||||
if waits++; waits == 10 {
|
||||
cancel()
|
||||
}
|
||||
return ctx.Err() == nil
|
||||
}
|
||||
// One at once, then one every 20 ms: about eleven in 210 ms, never a burst, never a stall.
|
||||
if len(lines) < 6 || len(lines) > 12 || lines[0] != "12.0 W machine" {
|
||||
if err := runDraw(ctx, m, 20*time.Millisecond, wait, func(r Reading) error { lines = append(lines, r.Text); return nil }); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// One at once, then one after each of the nine waits that passed; the tenth wait ended the loop.
|
||||
if len(lines) != 10 || lines[0] != "12.0 W machine" {
|
||||
t.Fatalf("%d lines: %v", len(lines), lines)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,11 +213,24 @@ func TestAMachineWithoutMainsSaysNothingAboutSupplies(t *testing.T) {
|
||||
func TestRunWithoutLogindStillSaysTheBoot(t *testing.T) {
|
||||
m := machine(t, map[string]string{"/proc/sys/kernel/random/boot_id": "xyz\n"})
|
||||
b := &bus{}
|
||||
w := NewWatcher(m, b.emit, func() (Login1, error) { return nil, errors.New("no system bus") })
|
||||
w.state = filepath.Join(t.TempDir(), "b")
|
||||
w.poll = 10 * time.Millisecond
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
// No timeout racing the loop: the run ends once logind was tried and the boot was published,
|
||||
// both of which happen in Run's own goroutine.
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
dialed, emitted := false, false
|
||||
stopWhenBoth := func() {
|
||||
if dialed && emitted {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
w := NewWatcher(m, func(typ string, body any) error {
|
||||
err := b.emit(typ, body)
|
||||
emitted = true
|
||||
stopWhenBoth()
|
||||
return err
|
||||
}, func() (Login1, error) { dialed = true; defer stopWhenBoth(); return nil, errors.New("no system bus") })
|
||||
w.state = filepath.Join(t.TempDir(), "b")
|
||||
w.poll = time.Millisecond
|
||||
w.Run(ctx)
|
||||
if got := b.seen(); len(got) == 0 || got[0] != Booted || w.Snapshot().Problem == "" {
|
||||
t.Fatalf("%v %+v", got, w.Snapshot())
|
||||
|
||||
Reference in New Issue
Block a user