You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by ja...@apache.org on 2026/10/23 22:28:41 UTC
(mynewt-newtmgr) 17/20: nmxact/task: Add unit tests for TaskQueue
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 <sz...@codecoup.pl>
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)
+ }
+ }
+}