github.com/safing/portbase@v0.19.5/modules/tasks_test.go (about)

     1  package modules
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"runtime/pprof"
     8  	"sync"
     9  	"testing"
    10  	"time"
    11  )
    12  
    13  func init() {
    14  	go taskQueueHandler()
    15  	go taskScheduleHandler()
    16  
    17  	go func() {
    18  		<-time.After(30 * time.Second)
    19  		fmt.Fprintln(os.Stderr, "taking too long")
    20  		_ = pprof.Lookup("goroutine").WriteTo(os.Stderr, 2)
    21  		os.Exit(1)
    22  	}()
    23  
    24  	// always trigger task timeslot for testing
    25  	go func() {
    26  		for {
    27  			taskTimeslot <- struct{}{}
    28  		}
    29  	}()
    30  }
    31  
    32  // Test queued tasks.
    33  
    34  // Queued task test globals.
    35  
    36  var (
    37  	qtWg            sync.WaitGroup
    38  	qtOutputChannel chan string
    39  	qtSleepDuration time.Duration
    40  	qtModule        *Module
    41  )
    42  
    43  func init() {
    44  	qtModule = initNewModule("task test module", nil, nil, nil)
    45  	qtModule.status = StatusOnline
    46  }
    47  
    48  // Queued task test functions.
    49  
    50  func queuedTaskTester(s string) {
    51  	qtModule.NewTask(s, func(ctx context.Context, t *Task) error {
    52  		time.Sleep(qtSleepDuration * 2)
    53  		qtOutputChannel <- s
    54  		qtWg.Done()
    55  		return nil
    56  	}).Queue()
    57  }
    58  
    59  func prioritizedTaskTester(s string) {
    60  	qtModule.NewTask(s, func(ctx context.Context, t *Task) error {
    61  		time.Sleep(qtSleepDuration * 2)
    62  		qtOutputChannel <- s
    63  		qtWg.Done()
    64  		return nil
    65  	}).QueuePrioritized()
    66  }
    67  
    68  func TestQueuedTask(t *testing.T) { //nolint:paralleltest // Too much interference expected.
    69  	// skip
    70  	if testing.Short() {
    71  		t.Skip("skipping test in short mode, as it is not fully deterministic")
    72  	}
    73  
    74  	// init
    75  	expectedOutput := "0123456789"
    76  	qtSleepDuration = 20 * time.Millisecond
    77  	qtOutputChannel = make(chan string, 100)
    78  	qtWg.Add(10)
    79  
    80  	// TEST
    81  	queuedTaskTester("0")
    82  	queuedTaskTester("1")
    83  	queuedTaskTester("3")
    84  	queuedTaskTester("4")
    85  	queuedTaskTester("6")
    86  	queuedTaskTester("7")
    87  	queuedTaskTester("9")
    88  
    89  	time.Sleep(qtSleepDuration * 3)
    90  	prioritizedTaskTester("2")
    91  	time.Sleep(qtSleepDuration * 6)
    92  	prioritizedTaskTester("5")
    93  	time.Sleep(qtSleepDuration * 6)
    94  	prioritizedTaskTester("8")
    95  
    96  	// wait for test to finish
    97  	qtWg.Wait()
    98  
    99  	// collect output
   100  	close(qtOutputChannel)
   101  	completeOutput := ""
   102  	for s := <-qtOutputChannel; s != ""; s = <-qtOutputChannel {
   103  		completeOutput += s
   104  	}
   105  	// check if test succeeded
   106  	if completeOutput != expectedOutput {
   107  		t.Errorf("QueuedTask test failed, expected sequence %s, got %s", expectedOutput, completeOutput)
   108  	}
   109  }
   110  
   111  // Test scheduled tasks.
   112  
   113  // Scheduled task test globals.
   114  
   115  var (
   116  	stWg            sync.WaitGroup
   117  	stOutputChannel chan string
   118  	stSleepDuration time.Duration
   119  	stWaitCh        chan bool
   120  )
   121  
   122  // Scheduled task test functions.
   123  
   124  func scheduledTaskTester(s string, sched time.Time) {
   125  	qtModule.NewTask(s, func(ctx context.Context, t *Task) error {
   126  		time.Sleep(stSleepDuration)
   127  		stOutputChannel <- s
   128  		stWg.Done()
   129  		return nil
   130  	}).Schedule(sched)
   131  }
   132  
   133  func TestScheduledTaskWaiting(t *testing.T) { //nolint:paralleltest // Too much interference expected.
   134  
   135  	// skip
   136  	if testing.Short() {
   137  		t.Skip("skipping test in short mode, as it is not fully deterministic")
   138  	}
   139  
   140  	// init
   141  	expectedOutput := "0123456789"
   142  	stSleepDuration = 10 * time.Millisecond
   143  	stOutputChannel = make(chan string, 100)
   144  	stWaitCh = make(chan bool)
   145  
   146  	stWg.Add(10)
   147  
   148  	// TEST
   149  	scheduledTaskTester("4", time.Now().Add(stSleepDuration*8))
   150  	scheduledTaskTester("0", time.Now().Add(stSleepDuration*0))
   151  	scheduledTaskTester("8", time.Now().Add(stSleepDuration*16))
   152  	scheduledTaskTester("1", time.Now().Add(stSleepDuration*2))
   153  	scheduledTaskTester("7", time.Now().Add(stSleepDuration*14))
   154  	scheduledTaskTester("9", time.Now().Add(stSleepDuration*18))
   155  	scheduledTaskTester("3", time.Now().Add(stSleepDuration*6))
   156  	scheduledTaskTester("2", time.Now().Add(stSleepDuration*4))
   157  	scheduledTaskTester("6", time.Now().Add(stSleepDuration*12))
   158  	scheduledTaskTester("5", time.Now().Add(stSleepDuration*10))
   159  
   160  	// wait for test to finish
   161  	close(stWaitCh)
   162  	stWg.Wait()
   163  
   164  	// collect output
   165  	close(stOutputChannel)
   166  	completeOutput := ""
   167  	for s := <-stOutputChannel; s != ""; s = <-stOutputChannel {
   168  		completeOutput += s
   169  	}
   170  	// check if test succeeded
   171  	if completeOutput != expectedOutput {
   172  		t.Errorf("ScheduledTask test failed, expected sequence %s, got %s", expectedOutput, completeOutput)
   173  	}
   174  }
   175  
   176  func TestRequeueingTask(t *testing.T) { //nolint:paralleltest // Too much interference expected.
   177  	blockWg := &sync.WaitGroup{}
   178  	wg := &sync.WaitGroup{}
   179  
   180  	// block task execution
   181  	blockWg.Add(1) // mark done at beginning
   182  	wg.Add(2)      // mark done at end
   183  	block := qtModule.NewTask("TestRequeueingTask:block", func(ctx context.Context, t *Task) error {
   184  		blockWg.Done()
   185  		time.Sleep(100 * time.Millisecond)
   186  		wg.Done()
   187  		return nil
   188  	}).StartASAP()
   189  	// make sure first task has started
   190  	blockWg.Wait()
   191  	// fmt.Printf("%s: %+v\n", time.Now(), block)
   192  
   193  	// schedule again while executing
   194  	blockWg.Add(1) // mark done at beginning
   195  	block.StartASAP()
   196  	// fmt.Printf("%s: %+v\n", time.Now(), block)
   197  
   198  	// test task
   199  	wg.Add(1)
   200  	task := qtModule.NewTask("TestRequeueingTask:test", func(ctx context.Context, t *Task) error {
   201  		wg.Done()
   202  		return nil
   203  	}).Schedule(time.Now().Add(2 * time.Second))
   204  
   205  	// reschedule
   206  	task.Schedule(time.Now().Add(1 * time.Second))
   207  	task.Queue()
   208  	task.QueuePrioritized()
   209  	task.StartASAP()
   210  	wg.Wait()
   211  	time.Sleep(100 * time.Millisecond) // let tasks finalize execution
   212  
   213  	// do it again
   214  
   215  	// block task execution (while first block task is still running!)
   216  	blockWg.Add(1) // mark done at beginning
   217  	wg.Add(1)      // mark done at end
   218  	block.StartASAP()
   219  	blockWg.Wait()
   220  	// reschedule
   221  	wg.Add(1)
   222  	task.Schedule(time.Now().Add(1 * time.Second))
   223  	task.Queue()
   224  	task.QueuePrioritized()
   225  	task.StartASAP()
   226  	wg.Wait()
   227  }
   228  
   229  func TestQueueSuccession(t *testing.T) { //nolint:paralleltest // Too much interference expected.
   230  	var cnt int
   231  	wg := &sync.WaitGroup{}
   232  	wg.Add(10)
   233  
   234  	tt := qtModule.NewTask("TestRequeueingTask:test", func(ctx context.Context, task *Task) error {
   235  		time.Sleep(10 * time.Millisecond)
   236  		wg.Done()
   237  		cnt++
   238  		fmt.Printf("completed succession %d\n", cnt)
   239  		switch cnt {
   240  		case 1, 4, 6:
   241  			task.Queue()
   242  		case 2, 5, 8:
   243  			task.StartASAP()
   244  		case 3, 7, 9:
   245  			task.Schedule(time.Now().Add(10 * time.Millisecond))
   246  		}
   247  		return nil
   248  	})
   249  	// fmt.Printf("%+v\n", tt)
   250  	tt.StartASAP()
   251  	// time.Sleep(100 * time.Millisecond)
   252  	// fmt.Printf("%+v\n", tt)
   253  
   254  	wg.Wait()
   255  }