github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/syz-cluster/controller/processor_test.go (about)

     1  // Copyright 2024 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package main
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/google/syzkaller/syz-cluster/pkg/api"
    14  	"github.com/google/syzkaller/syz-cluster/pkg/app"
    15  	"github.com/google/syzkaller/syz-cluster/pkg/controller"
    16  	"github.com/google/syzkaller/syz-cluster/pkg/db"
    17  	"github.com/google/syzkaller/syz-cluster/pkg/workflow"
    18  	"github.com/stretchr/testify/assert"
    19  )
    20  
    21  // It's a bit too long for a unit test, but it captures the whole main scenario of operation.
    22  func TestProcessor(t *testing.T) {
    23  	workflows := newMockedWorkflows()
    24  	processor, client, ctx := prepareProcessorTest(t, workflows)
    25  
    26  	// Start the loop.
    27  	var wg sync.WaitGroup
    28  	ctx2, cancel := context.WithCancel(ctx)
    29  	wg.Add(1)
    30  	go func() {
    31  		processor.Loop(ctx2)
    32  		wg.Done()
    33  	}()
    34  
    35  	// Add some series.
    36  	var allSeries []*api.Series
    37  	for i := 0; i < 10; i++ {
    38  		id := fmt.Sprintf("series-%d", i)
    39  		allSeries = append(allSeries, &api.Series{
    40  			ExtID: id,
    41  			Title: id,
    42  		})
    43  	}
    44  	for _, series := range allSeries[0:5] {
    45  		controller.UploadTestSeries(t, ctx, client, series)
    46  	}
    47  
    48  	// Let some workflows finish.
    49  	for i := 0; i < 2; i++ {
    50  		workflows.finish <- struct{}{}
    51  	}
    52  
    53  	awaitFinishedSessions(t, processor.seriesRepo, 2)
    54  
    55  	// Emulate the service restart by aborting the loop.
    56  	// This may break the execution in arbitrary places, which actually resembles the environment in which the code
    57  	// will actually work. The bugs it triggers may be difficult to reproduce though.
    58  	cancel()
    59  	wg.Wait()
    60  
    61  	ctx3, cancel := context.WithCancel(ctx)
    62  	wg.Add(1)
    63  	defer wg.Wait()
    64  	go func() {
    65  		processor.Loop(ctx3)
    66  		wg.Done()
    67  	}()
    68  
    69  	// Add some more series.
    70  	for _, series := range allSeries[5:10] {
    71  		controller.UploadTestSeries(t, ctx, client, series)
    72  	}
    73  
    74  	// Finish all of them.
    75  	for i := 0; i < 8; i++ {
    76  		workflows.finish <- struct{}{}
    77  	}
    78  
    79  	awaitFinishedSessions(t, processor.seriesRepo, 10)
    80  	cancel()
    81  }
    82  
    83  func TestFinishRunningSteps(t *testing.T) {
    84  	workflows := newMockedWorkflows()
    85  	processor, client, ctx := prepareProcessorTest(t, workflows)
    86  
    87  	// Start the loop.
    88  	var wg sync.WaitGroup
    89  	ctx2, cancel := context.WithCancel(ctx)
    90  	wg.Add(1)
    91  	go func() {
    92  		processor.Loop(ctx2)
    93  		wg.Done()
    94  	}()
    95  
    96  	series := &api.Series{
    97  		ExtID: "ext-id",
    98  		Title: "title",
    99  	}
   100  	ids := controller.UploadTestSeries(t, ctx, client, series)
   101  	buildResp := controller.UploadTestBuild(t, ctx, client, &api.Build{
   102  		Arch:       "amd64",
   103  		TreeName:   "mainline",
   104  		ConfigName: "config",
   105  		CommitHash: "abcd",
   106  	})
   107  	err := client.UploadTestResult(ctx, &api.TestResult{
   108  		SessionID:   ids.SessionID,
   109  		BaseBuildID: buildResp.ID,
   110  		TestName:    "test",
   111  		Result:      api.TestRunning,
   112  	})
   113  	assert.NoError(t, err)
   114  
   115  	// Let the workflow finish.
   116  	workflows.finish <- struct{}{}
   117  	awaitFinishedSessions(t, processor.seriesRepo, 1)
   118  	cancel()
   119  
   120  	// Verify that the session test is finished.
   121  	// A bit hacky, but it works.
   122  	list, err := processor.sessionTestRepo.BySessionRaw(ctx, ids.SessionID)
   123  	assert.NoError(t, err)
   124  	assert.Equal(t, api.TestError, list[0].Result)
   125  }
   126  
   127  func awaitFinishedSessions(t *testing.T, seriesRepo *db.SeriesRepository, wantFinished int) {
   128  	t.Logf("awaiting %d finished sessions", wantFinished)
   129  	deadline := time.Second * 2
   130  	interval := time.Second / 10
   131  	for i := 0; i < int(deadline/interval); i++ {
   132  		time.Sleep(interval)
   133  
   134  		list, err := seriesRepo.ListLatest(context.Background(), db.SeriesFilter{}, time.Time{})
   135  		assert.NoError(t, err)
   136  		withFinishedSeries := 0
   137  		for _, item := range list {
   138  			if item.Session == nil {
   139  				continue
   140  			}
   141  			if item.Session.FinishedAt.IsNull() {
   142  				continue
   143  			}
   144  			withFinishedSeries++
   145  		}
   146  		t.Logf("have %d finished", withFinishedSeries)
   147  		if withFinishedSeries == wantFinished {
   148  			return
   149  		}
   150  	}
   151  	t.Fatalf("never reached %d finished series", wantFinished)
   152  }
   153  
   154  type mockedWorkflows struct {
   155  	workflow.MockService
   156  	finish  chan struct{}
   157  	created map[string]struct{}
   158  }
   159  
   160  func newMockedWorkflows() *mockedWorkflows {
   161  	obj := mockedWorkflows{
   162  		finish:  make(chan struct{}),
   163  		created: make(map[string]struct{}),
   164  	}
   165  	obj.PollDelayValue = time.Millisecond
   166  	obj.OnStart = func(id string) error {
   167  		obj.created[id] = struct{}{}
   168  		return nil
   169  	}
   170  	obj.OnStatus = func(id string) (workflow.Status, []byte, error) {
   171  		_, ok := obj.created[id]
   172  		if !ok {
   173  			return workflow.StatusNotFound, nil, nil
   174  		}
   175  		finished := false
   176  		select {
   177  		case <-obj.finish:
   178  			finished = true
   179  		default:
   180  		}
   181  		if finished {
   182  			return workflow.StatusFinished, nil, nil
   183  		}
   184  		return workflow.StatusRunning, nil, nil
   185  	}
   186  	return &obj
   187  }
   188  
   189  func prepareProcessorTest(t *testing.T, workflows workflow.Service) (*SeriesProcessor,
   190  	*api.Client, context.Context) {
   191  	env, ctx := app.TestEnvironment(t)
   192  	client := controller.TestServer(t, env)
   193  	return &SeriesProcessor{
   194  		seriesRepo:        db.NewSeriesRepository(env.Spanner),
   195  		sessionRepo:       db.NewSessionRepository(env.Spanner),
   196  		sessionTestRepo:   db.NewSessionTestRepository(env.Spanner),
   197  		workflows:         workflows,
   198  		dbPollInterval:    time.Second / 10,
   199  		parallelWorkflows: 2,
   200  	}, client, ctx
   201  }