github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/dashboard/app/public_json_api_test.go (about)

     1  // Copyright 2021 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  	"bytes"
     8  	"context"
     9  	"fmt"
    10  	"testing"
    11  
    12  	"github.com/google/syzkaller/dashboard/api"
    13  	"github.com/google/syzkaller/dashboard/dashapi"
    14  	"github.com/google/syzkaller/pkg/coveragedb"
    15  	"github.com/google/syzkaller/pkg/coveragedb/mocks"
    16  	"github.com/google/syzkaller/pkg/coveragedb/spannerclient"
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/mock"
    19  )
    20  
    21  func TestJSONAPIIntegration(t *testing.T) {
    22  	sampleCrashDescr := []byte(`{
    23  	"version": 1,
    24  	"title": "title1",
    25  	"id": "cb1dbe55dc6daa7e739a0d09a0ae4d5e3e5a10c8",
    26  	"crashes": [
    27  		{
    28  			"title": "title1",
    29  			"kernel-config": "/text?tag=KernelConfig\u0026x=a989f27ebc47e2dc",
    30  			"kernel-source-commit": "1111111111111111111111111111111111111111",
    31  			"syzkaller-git": "https://github.com/google/syzkaller/commits/syzkaller_commit1",
    32  			"syzkaller-commit": "syzkaller_commit1",
    33  			"crash-report-link": "/text?tag=CrashReport\u0026x=12000000000000"
    34  		}
    35  	]
    36  }`,
    37  	)
    38  
    39  	sampleCrashWithReproDescr := []byte(`{
    40  	"version": 1,
    41  	"title": "title2",
    42  	"id": "fc00fbc0cddd9a4ef2ae33e40cd21636081466ce",
    43  	"crashes": [
    44  		{
    45  			"title": "title2",
    46  			"syz-reproducer": "/text?tag=ReproSyz\u0026x=13000000000000",
    47  			"c-reproducer": "/text?tag=ReproC\u0026x=17000000000000",
    48  			"kernel-config": "/text?tag=KernelConfig\u0026x=a989f27ebc47e2dc",
    49  			"kernel-source-commit": "1111111111111111111111111111111111111111",
    50  			"syzkaller-git": "https://github.com/google/syzkaller/commits/syzkaller_commit1",
    51  			"syzkaller-commit": "syzkaller_commit1",
    52  			"crash-report-link": "/text?tag=CrashReport\u0026x=15000000000000"
    53  		}
    54  	]
    55  }`,
    56  	)
    57  
    58  	sampleOpenBugGroupDescr := []byte(`{
    59  	"version": 1,
    60  	"Bugs": [
    61  		{
    62  			"title": "title1",
    63  			"link": "/bug?extid=decf42d66dced481afc1"
    64  		},
    65  		{
    66  			"title": "title2",
    67  			"link": "/bug?extid=0267d1c87b9ed4eb5def"
    68  		}
    69  	]
    70  }`)
    71  
    72  	sampleFixedBugGroupDescr := []byte(`{
    73  	"version": 1,
    74  	"Bugs": [
    75  		{
    76  			"title": "title2",
    77  			"link": "/bug?extid=0267d1c87b9ed4eb5def",
    78  			"fix-commits": [
    79  				{
    80  					"title": "foo: fix1",
    81  					"repo": "git://syzkaller.org",
    82  					"branch": "branch10"
    83  				},
    84  				{
    85  					"title": "foo: fix2",
    86  					"repo": "git://syzkaller.org",
    87  					"branch": "branch10"
    88  				}
    89  			]
    90  		}
    91  	]
    92  }`)
    93  
    94  	c := NewCtx(t)
    95  	defer c.Close()
    96  
    97  	c.makeClient(client1, password1, false)
    98  
    99  	build := testBuild(1)
   100  	c.client.UploadBuild(build)
   101  
   102  	crash1 := testCrash(build, 1)
   103  	c.client.ReportCrash(crash1)
   104  	bugReport1 := c.client.pollBug()
   105  	checkBugPageJSONIs(c, bugReport1.ID, sampleCrashDescr)
   106  
   107  	crash2 := testCrashWithRepro(build, 2)
   108  	c.client.ReportCrash(crash2)
   109  	bugReport2 := c.client.pollBug()
   110  	checkBugPageJSONIs(c, bugReport2.ID, sampleCrashWithReproDescr)
   111  
   112  	checkBugGroupPageJSONIs(c, "/test1?json=1", sampleOpenBugGroupDescr)
   113  
   114  	c.client.ReportingUpdate(&dashapi.BugUpdate{
   115  		ID:         bugReport2.ID,
   116  		Status:     dashapi.BugStatusOpen,
   117  		FixCommits: []string{"foo: fix1", "foo: fix2"},
   118  	})
   119  
   120  	checkBugGroupPageJSONIs(c, "/test1/fixed?json=1", sampleFixedBugGroupDescr)
   121  }
   122  
   123  func checkBugPageJSONIs(c *Ctx, ID string, expectedContent []byte) {
   124  	url := fmt.Sprintf("/bug?extid=%v&json=1", ID)
   125  
   126  	contentType, _ := c.client.ContentType(url)
   127  	c.expectEQ(contentType, "application/json")
   128  
   129  	actualContent, _ := c.client.GET(url)
   130  	c.expectEQ(string(actualContent), string(expectedContent))
   131  }
   132  
   133  func checkBugGroupPageJSONIs(c *Ctx, url string, expectedContent []byte) {
   134  	contentType, _ := c.client.ContentType(url)
   135  	c.expectEQ(contentType, "application/json")
   136  
   137  	actualContent, _ := c.client.GET(url)
   138  	c.expectEQ(string(actualContent), string(expectedContent))
   139  }
   140  
   141  func TestJSONAPIFixCommits(t *testing.T) {
   142  	c := NewCtx(t)
   143  	defer c.Close()
   144  
   145  	build1 := testBuild(1)
   146  	c.client.UploadBuild(build1)
   147  
   148  	crash1 := testCrash(build1, 1)
   149  	c.client.ReportCrash(crash1)
   150  	rep1 := c.client.pollBug()
   151  
   152  	// Specify fixing commit for the bug.
   153  	c.client.ReportingUpdate(&dashapi.BugUpdate{
   154  		ID:         rep1.ID,
   155  		Status:     dashapi.BugStatusOpen,
   156  		FixCommits: []string{"foo: fix1", "foo: fix2"},
   157  	})
   158  
   159  	c.client.UploadCommits([]dashapi.Commit{
   160  		{Hash: "hash1", Title: "foo: fix1"},
   161  	})
   162  
   163  	c.client.CommitPoll()
   164  
   165  	want := []byte(`{
   166  	"version": 1,
   167  	"title": "title1",
   168  	"id": "cb1dbe55dc6daa7e739a0d09a0ae4d5e3e5a10c8",
   169  	"fix-commits": [
   170  		{
   171  			"title": "foo: fix1",
   172  			"hash": "hash1",
   173  			"repo": "git://syzkaller.org",
   174  			"branch": "branch10"
   175  		},
   176  		{
   177  			"title": "foo: fix2",
   178  			"repo": "git://syzkaller.org",
   179  			"branch": "branch10"
   180  		}
   181  	],
   182  	"crashes": [
   183  		{
   184  			"title": "title1",
   185  			"kernel-config": "/text?tag=KernelConfig\u0026x=a989f27ebc47e2dc",
   186  			"kernel-source-commit": "1111111111111111111111111111111111111111",
   187  			"syzkaller-git": "https://github.com/google/syzkaller/commits/syzkaller_commit1",
   188  			"syzkaller-commit": "syzkaller_commit1",
   189  			"crash-report-link": "/text?tag=CrashReport\u0026x=12000000000000"
   190  		}
   191  	]
   192  }`)
   193  	checkBugGroupPageJSONIs(c, "/bug?extid=decf42d66dced481afc1&json=1", want)
   194  }
   195  
   196  func TestJSONAPICauseBisection(t *testing.T) {
   197  	c := NewCtx(t)
   198  	defer c.Close()
   199  
   200  	build, _ := addBuildAndCrash(c)
   201  	_, bugKey := c.loadSingleBug()
   202  
   203  	addBisectCauseJob(c, build)
   204  	addBisectFixJob(c, build)
   205  
   206  	url := fmt.Sprintf("/bug?id=%v&json=1", bugKey.StringID())
   207  	content, err := c.GET(url)
   208  	c.expectEQ(err, nil)
   209  	c.expectEQ(string(content), `{
   210  	"version": 1,
   211  	"title": "title1",
   212  	"id": "70ce63ecb151d563976728208edccc6879191f9f",
   213  	"cause-commit": {
   214  		"title": "kernel: add a bug",
   215  		"hash": "36e65cb4a0448942ec316b24d60446bbd5cc7827",
   216  		"repo": "repo1",
   217  		"branch": "branch1"
   218  	},
   219  	"crashes": [
   220  		{
   221  			"title": "title1",
   222  			"syz-reproducer": "/text?tag=ReproSyz\u0026x=16000000000000",
   223  			"c-reproducer": "/text?tag=ReproC\u0026x=11000000000000",
   224  			"kernel-config": "/text?tag=KernelConfig\u0026x=4d11162a90e18f28",
   225  			"kernel-source-commit": "1111111111111111111111111111111111111111",
   226  			"syzkaller-git": "https://github.com/google/syzkaller/commits/syzkaller_commit1",
   227  			"syzkaller-commit": "syzkaller_commit1",
   228  			"crash-report-link": "/text?tag=CrashReport\u0026x=12000000000000"
   229  		}
   230  	]
   231  }`)
   232  }
   233  
   234  func TestPublicJSONAPI(t *testing.T) {
   235  	c := NewCtx(t)
   236  	defer c.Close()
   237  
   238  	client := c.makeClient(clientPublic, keyPublic, true)
   239  	build := testBuild(1)
   240  	client.UploadBuild(build)
   241  	client.ReportCrash(testCrashWithRepro(build, 1))
   242  	rep := client.pollBug()
   243  	client.updateBug(rep.ID, dashapi.BugStatusUpstream, "")
   244  	_ = client.pollBug()
   245  
   246  	cli := c.makeAPIClient()
   247  	bugs, err := cli.BugGroups("access-public", api.BugGroupAll)
   248  	c.expectOK(err)
   249  	c.expectEQ(len(bugs), 1)
   250  	c.expectEQ(bugs[0].Title, "title1")
   251  
   252  	bug, err := cli.Bug(bugs[0].Link)
   253  	c.expectOK(err)
   254  	c.expectEQ(bug.Title, "title1")
   255  
   256  	config, err := cli.Text(bug.Crashes[0].KernelConfigLink)
   257  	c.expectOK(err)
   258  	c.expectEQ(config, []byte("config1"))
   259  }
   260  
   261  func TestWriteExtAPICoverageFor(t *testing.T) {
   262  	ctx := setCoverageDBClient(context.Background(), fileFuncLinesDBFixture(t,
   263  		[]*coveragedb.FuncLines{
   264  			{
   265  				FilePath: "/file",
   266  				FuncName: "func_name",
   267  				Lines:    []int64{1, 2, 3, 4},
   268  			},
   269  		},
   270  		[]*coveragedb.FileCoverageWithLineInfo{
   271  			{
   272  				FileCoverageWithDetails: coveragedb.FileCoverageWithDetails{
   273  					Filepath: "/file",
   274  					Commit:   "test-commit",
   275  				},
   276  				LinesInstrumented: []int64{1, 2, 3, 4},
   277  				HitCounts:         []int64{10, 20, 30, 0},
   278  			},
   279  		},
   280  	))
   281  
   282  	var buf bytes.Buffer
   283  	err := writeExtAPICoverageFor(ctx, &buf, "test-ns", "test-repo", nil)
   284  	assert.NoError(t, err)
   285  	assert.Equal(t, `{
   286  	"repo": "test-repo",
   287  	"commit": "test-commit",
   288  	"file_path": "/file",
   289  	"functions": [
   290  		{
   291  			"func_name": "func_name",
   292  			"blocks": [
   293  				{
   294  					"hit_count": 10,
   295  					"from_line": 1,
   296  					"from_column": 0,
   297  					"to_line": 1,
   298  					"to_column": -1
   299  				},
   300  				{
   301  					"hit_count": 20,
   302  					"from_line": 2,
   303  					"from_column": 0,
   304  					"to_line": 2,
   305  					"to_column": -1
   306  				},
   307  				{
   308  					"hit_count": 30,
   309  					"from_line": 3,
   310  					"from_column": 0,
   311  					"to_line": 3,
   312  					"to_column": -1
   313  				},
   314  				{
   315  					"from_line": 4,
   316  					"from_column": 0,
   317  					"to_line": 4,
   318  					"to_column": -1
   319  				}
   320  			]
   321  		}
   322  	]
   323  }
   324  `, buf.String())
   325  }
   326  
   327  func fileFuncLinesDBFixture(t *testing.T, funcLines []*coveragedb.FuncLines,
   328  	fileCovWithLineInfo []*coveragedb.FileCoverageWithLineInfo) spannerclient.SpannerClient {
   329  	mPartialTran := mocks.NewReadOnlyTransaction(t)
   330  	mPartialTran.On("Query", mock.Anything, mock.Anything).
   331  		Return(newRowIteratorMock(t, funcLines)).Once()
   332  
   333  	mFullTran := mocks.NewReadOnlyTransaction(t)
   334  	mFullTran.On("Query", mock.Anything, mock.Anything).
   335  		Return(newRowIteratorMock(t, fileCovWithLineInfo)).Once()
   336  
   337  	m := mocks.NewSpannerClient(t)
   338  	m.On("Single").
   339  		Return(mPartialTran).Once()
   340  	m.On("Single").
   341  		Return(mFullTran).Once()
   342  	return m
   343  }