This is an automated email from the ASF dual-hosted git repository. sjanc pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mynewt-newtmgr.git
commit 278e091b592ae0046f0b655a96353a9a9642b83c Author: Szymon Janc <[email protected]> AuthorDate: Thu Oct 22 00:35:57 2026 +0200 nmxact/task: Add unit tests for TaskQueue Cover Enqueue/Run on an inactive queue returning InactiveError, serial execution in submission order with per-job results, double Start/Stop errors, Stop failing queued jobs with the given cause while waiting for the running job, StopNoWait from within a job, and restarting a queue after Stop. Assisted-by: Claude:claude-opus-5-5 --- nmxact/task/task_test.go | 240 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) diff --git a/nmxact/task/task_test.go b/nmxact/task/task_test.go new file mode 100644 index 0000000..28ed05d --- /dev/null +++ b/nmxact/task/task_test.go @@ -0,0 +1,240 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package task + +import ( + "errors" + "testing" + "time" +) + +const testTimeout = 5 * time.Second + +func recvResult(t *testing.T, ch chan error) error { + t.Helper() + + select { + case err := <-ch: + return err + case <-time.After(testTimeout): + t.Fatalf("timeout waiting for job result") + } + return nil +} + +func stopWithTimeout(t *testing.T, q *TaskQueue, cause error) error { + t.Helper() + + res := make(chan error, 1) + go func() { res <- q.Stop(cause) }() + return recvResult(t, res) +} + +func TestEnqueueInactive(t *testing.T) { + q := NewTaskQueue("test") + + ran := false + ch := q.Enqueue(func() error { + ran = true + return nil + }) + if err := recvResult(t, ch); err != InactiveError { + t.Fatalf("got %v, want InactiveError", err) + } + if err := q.Run(func() error { return nil }); err != InactiveError { + t.Fatalf("Run got %v, want InactiveError", err) + } + if ran { + t.Errorf("job ran on inactive queue") + } + if q.Active() { + t.Errorf("new queue is active") + } +} + +func TestRunOrderAndResults(t *testing.T) { + q := NewTaskQueue("test") + if err := q.Start(8); err != nil { + t.Fatal(err) + } + defer stopWithTimeout(t, &q, errors.New("done")) + + if !q.Active() { + t.Fatalf("started queue not active") + } + + // Only touched by the task goroutine; read after all jobs completed. + var order []int + var chs []chan error + jobErr := errors.New("job 3 failed") + for i := 0; i < 5; i++ { + i := i + chs = append(chs, q.Enqueue(func() error { + order = append(order, i) + if i == 3 { + return jobErr + } + return nil + })) + } + + for i, ch := range chs { + want := error(nil) + if i == 3 { + want = jobErr + } + if err := recvResult(t, ch); err != want { + t.Errorf("job %d returned %v, want %v", i, err, want) + } + if _, ok := <-ch; ok { + t.Errorf("job %d result channel not closed", i) + } + } + + for i, v := range order { + if v != i { + t.Fatalf("jobs ran in order %v", order) + } + } + if len(order) != 5 { + t.Fatalf("%d jobs ran, want 5", len(order)) + } +} + +func TestStartStopTwice(t *testing.T) { + q := NewTaskQueue("test") + + if err := q.Stop(errors.New("x")); err == nil { + t.Errorf("Stop of unstarted queue succeeded") + } + if err := q.Start(1); err != nil { + t.Fatal(err) + } + if err := q.Start(1); err == nil { + t.Errorf("second Start succeeded") + } + if err := stopWithTimeout(t, &q, errors.New("x")); err != nil { + t.Fatalf("Stop failed: %v", err) + } + if err := q.Stop(errors.New("x")); err == nil { + t.Errorf("second Stop succeeded") + } + if err := q.StopNoWait(errors.New("x")); err == nil { + t.Errorf("StopNoWait of stopped queue succeeded") + } +} + +func TestStopFailsPendingJobs(t *testing.T) { + q := NewTaskQueue("test") + if err := q.Start(4); err != nil { + t.Fatal(err) + } + + running := make(chan struct{}) + release := make(chan struct{}) + ch1 := q.Enqueue(func() error { + close(running) + <-release + return nil + }) + + select { + case <-running: + case <-time.After(testTimeout): + t.Fatalf("first job did not start") + } + + ran := make(chan int, 2) + ch2 := q.Enqueue(func() error { ran <- 2; return nil }) + ch3 := q.Enqueue(func() error { ran <- 3; return nil }) + + cause := errors.New("stopping") + stopped := make(chan error, 1) + go func() { stopped <- q.Stop(cause) }() + + // Pending jobs fail with the cause without running. + for i, ch := range []chan error{ch2, ch3} { + if err := recvResult(t, ch); err != cause { + t.Errorf("pending job %d got %v, want %v", i+2, err, cause) + } + } + + // Stop waits for the running job. + select { + case err := <-stopped: + t.Fatalf("Stop returned (%v) while a job was running", err) + default: + } + + close(release) + if err := recvResult(t, ch1); err != nil { + t.Errorf("running job got %v", err) + } + if err := recvResult(t, stopped); err != nil { + t.Fatalf("Stop failed: %v", err) + } + + if len(ran) != 0 { + t.Errorf("pending job ran after Stop") + } + if q.Active() { + t.Errorf("queue still active after Stop") + } + ch := q.Enqueue(func() error { return nil }) + if err := recvResult(t, ch); err != InactiveError { + t.Errorf("Enqueue after Stop got %v, want InactiveError", err) + } +} + +func TestStopNoWaitFromJob(t *testing.T) { + q := NewTaskQueue("test") + if err := q.Start(1); err != nil { + t.Fatal(err) + } + + cause := errors.New("job stopped queue") + err := recvResult(t, q.Enqueue(func() error { + return q.StopNoWait(cause) + })) + if err != nil { + t.Fatalf("StopNoWait from job failed: %v", err) + } + + if q.Active() { + t.Errorf("queue still active") + } +} + +func TestRestartAfterStop(t *testing.T) { + q := NewTaskQueue("test") + + for i := 0; i < 3; i++ { + if err := q.Start(1); err != nil { + t.Fatalf("Start %d failed: %v", i, err) + } + want := errors.New("result") + if err := q.Run(func() error { return want }); err != want { + t.Fatalf("Run %d got %v, want %v", i, err, want) + } + if err := stopWithTimeout(t, &q, errors.New("x")); err != nil { + t.Fatalf("Stop %d failed: %v", i, err) + } + } +}
