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 }