From 018ebef2bbad9aa0e117fb8e7768c481e0dd4dd4 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Mon, 2 Feb 2026 08:12:51 +0100 Subject: dashboard/app: improve TestLocalUI - allow to see UI as admin/user/none - populate DB before opening browser - don't log requests for favicon.ico - fix http handler registration for the test namespace - populate database with slightly more realistic data --- dashboard/app/app_test.go | 10 +++++++++ dashboard/app/local_ui_test.go | 49 +++++++++++++++++++++++++++++++++++------- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/dashboard/app/app_test.go b/dashboard/app/app_test.go index cfabcc473..53b121462 100644 --- a/dashboard/app/app_test.go +++ b/dashboard/app/app_test.go @@ -36,6 +36,16 @@ func init() { notifyAboutUnsuccessfulBisections = true ensureConfigImmutability = true initMocks() + // Insert namespaces from localUIConfig into testConfig. + // This is required b/c main.go registers HTTP handlers for all namespaces + // in the main installed config only, so if we don't do that the localUIConfig + // namespaces won't have handlers. // It's not important if there are duplicates + // b/c registration code mostly looks at namespace names. + for ns, cfg := range localUIConfig.Namespaces { + if testConfig.Namespaces[ns] == nil { + testConfig.Namespaces[ns] = cfg + } + } installConfig(testConfig) } diff --git a/dashboard/app/local_ui_test.go b/dashboard/app/local_ui_test.go index e17f46fac..610b5ff45 100644 --- a/dashboard/app/local_ui_test.go +++ b/dashboard/app/local_ui_test.go @@ -16,6 +16,7 @@ import ( "time" "github.com/google/syzkaller/dashboard/dashapi" + "github.com/google/syzkaller/pkg/aflow/ai" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/sys/targets" "github.com/stretchr/testify/require" @@ -25,6 +26,7 @@ import ( var ( flagLocalUI = flag.Bool("local-ui", false, "start local web server in the TestLocalUI test") flagLocalUIAddr = flag.String("local-ui-addr", "127.0.0.1:0", "run the web server on this network address") + flagLocalUIUser = flag.String("local-ui-user", "admin", "authenticate requests as admin/user/none") ) // Run the test with: @@ -48,20 +50,20 @@ func TestLocalUI(t *testing.T) { c.transformContext = func(ctx context.Context) context.Context { return contextWithConfig(ctx, localUIConfig) } + populateLocalUIDB(t, c) ln, err := net.Listen("tcp4", *flagLocalUIAddr) require.NoError(t, err) url := fmt.Sprintf("http://%v", ln.Addr()) exec.Command("xdg-open", url+"/linux").Start() go func() { - populateLocalUIDB(t, c) // Let the dev_appserver print tons of unuseful garbage to the console // before we print the serving address, so it's possible to find it in all the garbage. - time.Sleep(2 * time.Second) + time.Sleep(3 * time.Second) t.Logf("serving http on %v", url) }() require.NoError(t, http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { url := r.URL.String() - if file := filepath.Join(".", url); url != "/" && osutil.IsExist(file) { + if file := filepath.Join(".", url); url != "/" && (osutil.IsExist(file) || url == "/favicon.ico") { http.ServeFile(w, r, file) return } @@ -72,7 +74,12 @@ func TestLocalUI(t *testing.T) { req.Header.Add("X-Appengine-User-IP", "127.0.0.1") req = req.WithContext(c.transformContext(req.Context())) req = registerRequest(req, c) - aetest.Login(makeUser(AuthorizedAdmin), req) + switch *flagLocalUIUser { + case "admin": + aetest.Login(makeUser(AuthorizedAdmin), req) + case "user": + aetest.Login(makeUser(AuthorizedUser), req) + } http.DefaultServeMux.ServeHTTP(w, req) }))) } @@ -87,7 +94,7 @@ var localUIConfig = &GlobalConfig{ AI: true, Key: password1, Clients: map[string]string{ - client1: password1, + localUIClient: localUIPassword, }, Repos: []KernelRepo{ { @@ -112,8 +119,18 @@ var localUIConfig = &GlobalConfig{ }, } +const ( + localUIClient = "local_ui_client" + localUIPassword = "localuipasswordlocaluipasswordlocaluipassword" +) + func populateLocalUIDB(t *testing.T, c *Ctx) { - client := c.makeClient(client1, password1, true) + client := c.makeClient(localUIClient, localUIPassword, true) + bugTitles := []string{ + "KASAN: slab-use-after-free Write in nr_neigh_put", + "KCSAN: data-race in mISDN_ioctl / mISDN_read", + "WARNING in raw_ioctl", + } for buildID := 0; buildID < 3; buildID++ { build := &dashapi.Build{ Manager: fmt.Sprintf("manager%v", buildID), @@ -131,11 +148,11 @@ func populateLocalUIDB(t *testing.T, c *Ctx) { KernelConfig: []byte(fmt.Sprintf("config%v", buildID)), } client.UploadBuild(build) - for bugID := 0; bugID < 3; bugID++ { + for bugID := 0; bugID < len(bugTitles); bugID++ { for crashID := 0; crashID < 3; crashID++ { client.ReportCrash(&dashapi.Crash{ BuildID: build.ID, - Title: fmt.Sprintf("title %v %v", bugID, crashID), + Title: bugTitles[bugID], Log: []byte(fmt.Sprintf("log %v %v", bugID, crashID)), Report: []byte(fmt.Sprintf("report %v %v", bugID, crashID)), MachineInfo: []byte(fmt.Sprintf("machine info %v %v", bugID, crashID)), @@ -147,4 +164,20 @@ func populateLocalUIDB(t *testing.T, c *Ctx) { } } } + resp, _ := client.AIJobPoll(&dashapi.AIJobPollReq{ + CodeRevision: "xxx", + Workflows: []dashapi.AIWorkflow{ + {Type: ai.WorkflowPatching, Name: string(ai.WorkflowPatching)}, + {Type: ai.WorkflowModeration, Name: string(ai.WorkflowModeration)}, + {Type: ai.WorkflowAssessmentKCSAN, Name: string(ai.WorkflowAssessmentKCSAN)}, + }, + }) + client.AIJobDone(&dashapi.AIJobDoneReq{ + ID: resp.ID, + Results: map[string]any{ + "Benign": false, + "Confident": true, + "Explanation": "ISO C says data races result in undefined program behavior.", + }, + }) } -- cgit mrf-deployment