go-hep.org/x/hep@v0.38.1/groot/rsrv/rsrv_test.go (about) 1 // Copyright ©2018 The go-hep Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package rsrv 6 7 import ( 8 "bytes" 9 "encoding/base64" 10 "encoding/json" 11 "image/color" 12 "io" 13 "log" 14 "mime/multipart" 15 "net/http" 16 "net/http/httptest" 17 "os" 18 "path/filepath" 19 "reflect" 20 "runtime" 21 "sort" 22 "strings" 23 "testing" 24 "time" 25 26 uuid "github.com/hashicorp/go-uuid" 27 "go-hep.org/x/hep/groot/internal/rtests" 28 _ "go-hep.org/x/hep/groot/riofs/plugin/http" 29 _ "go-hep.org/x/hep/groot/riofs/plugin/xrootd" 30 "gonum.org/v1/plot/cmpimg" 31 ) 32 33 var ( 34 srv *Server 35 ) 36 37 func TestMain(m *testing.M) { 38 dir, err := os.MkdirTemp("", "groot-rsrv-") 39 if err != nil { 40 log.Panicf("could not create temporary directory: %v", err) 41 } 42 defer os.RemoveAll(dir) 43 44 srv = New(dir) 45 setupCookie(srv) 46 47 os.Exit(m.Run()) 48 } 49 50 func newTestServer() *httptest.Server { 51 mux := http.NewServeMux() 52 mux.HandleFunc("/open-file", srv.OpenFile) 53 mux.HandleFunc("/upload-file", srv.UploadFile) 54 mux.HandleFunc("/close-file", srv.CloseFile) 55 mux.HandleFunc("/list-files", srv.ListFiles) 56 mux.HandleFunc("/list-dirs", srv.Dirent) 57 mux.HandleFunc("/list-tree", srv.Tree) 58 mux.HandleFunc("/plot-h1", srv.PlotH1) 59 mux.HandleFunc("/plot-h2", srv.PlotH2) 60 mux.HandleFunc("/plot-s2", srv.PlotS2) 61 mux.HandleFunc("/plot-tree", srv.PlotTree) 62 63 return httptest.NewServer(mux) 64 } 65 66 func TestOpenFile(t *testing.T) { 67 ts := newTestServer() 68 defer ts.Close() 69 70 local, err := filepath.Abs("../testdata/simple.root") 71 if err != nil { 72 t.Fatalf("%+v", err) 73 } 74 75 for _, tc := range []struct { 76 uri string 77 status int 78 }{ 79 {"https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/simple.root", http.StatusOK}, 80 {rtests.XrdRemote("testdata/simple.root"), http.StatusOK}, 81 {"file://" + local, http.StatusOK}, 82 } { 83 t.Run(tc.uri, func(t *testing.T) { 84 testOpenFile(t, ts, tc.uri, tc.status) 85 defer testCloseFile(t, ts, tc.uri) 86 }) 87 } 88 } 89 90 func TestDoubleOpenFile(t *testing.T) { 91 ts := newTestServer() 92 defer ts.Close() 93 94 testOpenFile(t, ts, "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/simple.root", 0) 95 testOpenFile(t, ts, "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/simple.root", http.StatusConflict) 96 } 97 98 func testOpenFile(t *testing.T, ts *httptest.Server, uri string, status int) { 99 t.Helper() 100 101 req := OpenFileRequest{URI: uri} 102 103 body := new(bytes.Buffer) 104 err := json.NewEncoder(body).Encode(req) 105 if err != nil { 106 t.Fatalf("could not encode request: %v", err) 107 } 108 109 hreq, err := http.NewRequest(http.MethodPost, ts.URL+"/open-file", body) 110 if err != nil { 111 t.Fatalf("could not create http request: %v", err) 112 } 113 srv.addCookies(hreq) 114 115 hresp, err := ts.Client().Do(hreq) 116 if err != nil { 117 t.Fatalf("could not post http request: %v", err) 118 } 119 defer hresp.Body.Close() 120 121 if got, want := hresp.StatusCode, status; got != want && want != 0 { 122 t.Fatalf("invalid status code: got=%v, want=%v", got, want) 123 } 124 } 125 126 func TestUploadFile(t *testing.T) { 127 ts := newTestServer() 128 defer ts.Close() 129 130 local, err := filepath.Abs("../testdata/simple.root") 131 if err != nil { 132 t.Fatalf("%+v", err) 133 } 134 135 for _, tc := range []struct { 136 dst, src string 137 status int 138 }{ 139 {"foo.root", local, http.StatusOK}, 140 } { 141 t.Run(tc.dst, func(t *testing.T) { 142 testUploadFile(t, ts, tc.dst, tc.src, tc.status) 143 defer testCloseFile(t, ts, tc.dst) 144 }) 145 } 146 } 147 148 func testUploadFile(t *testing.T, ts *httptest.Server, dst, src string, status int) { 149 t.Helper() 150 151 body := new(bytes.Buffer) 152 mpart := multipart.NewWriter(body) 153 req, err := mpart.CreateFormField("groot-dst") 154 if err != nil { 155 t.Fatalf("could not create json-request form field: %v", err) 156 } 157 _, err = req.Write([]byte(dst)) 158 if err != nil { 159 t.Fatalf("could not fill destination field: %v", err) 160 } 161 162 w, err := mpart.CreateFormFile("groot-file", src) 163 if err != nil { 164 t.Fatalf("could not create form-file: %v", err) 165 } 166 { 167 f, err := os.Open(src) 168 if err != nil { 169 t.Fatalf("%+v", err) 170 } 171 defer f.Close() 172 173 _, err = io.CopyBuffer(w, f, make([]byte, 16*1024*1024)) 174 if err != nil { 175 t.Fatalf("could not copy file: %v", err) 176 } 177 } 178 179 if err := mpart.Close(); err != nil { 180 t.Fatalf("could not close multipart form data: %v", err) 181 } 182 183 hreq, err := http.NewRequest(http.MethodPost, ts.URL+"/upload-file", body) 184 if err != nil { 185 t.Fatalf("could not create http request: %v", err) 186 } 187 srv.addCookies(hreq) 188 hreq.Header.Set("Content-Type", mpart.FormDataContentType()) 189 190 hresp, err := ts.Client().Do(hreq) 191 if err != nil { 192 t.Fatalf("could not post http request: %v", err) 193 } 194 defer hresp.Body.Close() 195 196 if got, want := hresp.StatusCode, status; got != want && want != 0 { 197 t.Fatalf("invalid status code: got=%v, want=%v", got, want) 198 } 199 } 200 201 func TestCloseFile(t *testing.T) { 202 ts := newTestServer() 203 defer ts.Close() 204 205 testOpenFile(t, ts, "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/simple.root", 0) 206 testCloseFile(t, ts, "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/simple.root") 207 testOpenFile(t, ts, "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/simple.root", http.StatusOK) 208 testCloseFile(t, ts, "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/simple.root") 209 } 210 211 func testCloseFile(t *testing.T, ts *httptest.Server, uri string) { 212 t.Helper() 213 214 req := CloseFileRequest{URI: uri} 215 body := new(bytes.Buffer) 216 err := json.NewEncoder(body).Encode(req) 217 if err != nil { 218 t.Fatalf("could not encode request: %v", err) 219 } 220 221 hreq, err := http.NewRequest(http.MethodPost, ts.URL+"/close-file", body) 222 if err != nil { 223 t.Fatalf("could not create http request: %v", err) 224 } 225 srv.addCookies(hreq) 226 227 hresp, err := ts.Client().Do(hreq) 228 if err != nil { 229 t.Fatalf("could not post http request: %v", err) 230 } 231 defer hresp.Body.Close() 232 233 if hresp.StatusCode != http.StatusOK { 234 t.Fatalf("could not close file %q: %v", uri, hresp.StatusCode) 235 } 236 } 237 238 func TestListFiles(t *testing.T) { 239 ts := newTestServer() 240 defer ts.Close() 241 242 testOpenFile(t, ts, "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/simple.root", 0) 243 testOpenFile(t, ts, "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/dirs-6.14.00.root", http.StatusOK) 244 testListFiles(t, ts, []File{ 245 {"https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/simple.root", 60600}, 246 {"https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/dirs-6.14.00.root", 61400}, 247 }) 248 testCloseFile(t, ts, "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/simple.root") 249 testCloseFile(t, ts, "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/dirs-6.14.00.root") 250 } 251 252 func testListFiles(t *testing.T, ts *httptest.Server, want []File) { 253 t.Helper() 254 255 hreq, err := http.NewRequest(http.MethodPost, ts.URL+"/list-files", nil) 256 if err != nil { 257 t.Fatalf("could not create http request: %v", err) 258 } 259 srv.addCookies(hreq) 260 261 hresp, err := ts.Client().Do(hreq) 262 if err != nil { 263 t.Fatalf("could not post http request: %v", err) 264 } 265 defer hresp.Body.Close() 266 267 if hresp.StatusCode != http.StatusOK { 268 t.Fatalf("could not list files: %v", hresp.StatusCode) 269 } 270 271 var resp ListResponse 272 err = json.NewDecoder(hresp.Body).Decode(&resp) 273 if err != nil { 274 t.Fatalf("could not decode response: %v", err) 275 } 276 277 got := resp.Files 278 sort.Slice(got, func(i, j int) bool { 279 return got[i].URI < got[j].URI 280 }) 281 sort.Slice(want, func(i, j int) bool { 282 return want[i].URI < want[j].URI 283 }) 284 285 if !reflect.DeepEqual(got, want) { 286 t.Fatalf("invalid ls content:\ngot= %v\nwant=%v\n", got, want) 287 } 288 } 289 290 func TestDirent(t *testing.T) { 291 ts := newTestServer() 292 defer ts.Close() 293 294 testOpenFile(t, ts, "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/simple.root", http.StatusOK) 295 defer testCloseFile(t, ts, "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/simple.root") 296 297 testDirent(t, ts, DirentRequest{ 298 URI: "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/simple.root", 299 Dir: "/", 300 Recursive: false, 301 }, []string{ 302 "/", 303 "/tree", 304 }) 305 306 testOpenFile(t, ts, "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/dirs-6.14.00.root", http.StatusOK) 307 defer testCloseFile(t, ts, "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/dirs-6.14.00.root") 308 309 testDirent(t, ts, DirentRequest{ 310 URI: "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/dirs-6.14.00.root", 311 Dir: "/", 312 Recursive: false, 313 }, []string{ 314 "/", 315 "/dir1", 316 "/dir2", 317 "/dir3", 318 }) 319 testDirent(t, ts, DirentRequest{ 320 URI: "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/dirs-6.14.00.root", 321 Dir: "/", 322 Recursive: true, 323 }, []string{ 324 "/", 325 "/dir1", 326 "/dir1/dir11", 327 "/dir1/dir11/h1", 328 "/dir2", 329 "/dir3", 330 }) 331 testDirent(t, ts, DirentRequest{ 332 URI: "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/dirs-6.14.00.root", 333 Dir: "/dir1", 334 Recursive: false, 335 }, []string{ 336 "/dir1", 337 "/dir1/dir11", 338 }) 339 testDirent(t, ts, DirentRequest{ 340 URI: "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/dirs-6.14.00.root", 341 Dir: "/dir1", 342 Recursive: true, 343 }, []string{ 344 "/dir1", 345 "/dir1/dir11", 346 "/dir1/dir11/h1", 347 }) 348 } 349 350 func testDirent(t *testing.T, ts *httptest.Server, req DirentRequest, content []string) { 351 t.Helper() 352 353 body := new(bytes.Buffer) 354 err := json.NewEncoder(body).Encode(req) 355 if err != nil { 356 t.Fatalf("could not encode request: %v", err) 357 } 358 359 hreq, err := http.NewRequest(http.MethodPost, ts.URL+"/list-dirs", body) 360 if err != nil { 361 t.Fatalf("could not create http request: %v", err) 362 } 363 srv.addCookies(hreq) 364 365 hresp, err := ts.Client().Do(hreq) 366 if err != nil { 367 t.Fatalf("could not post http request: %v", err) 368 } 369 defer hresp.Body.Close() 370 371 if hresp.StatusCode != http.StatusOK { 372 t.Fatalf("could not list dirs: %v", hresp.StatusCode) 373 } 374 375 var resp DirentResponse 376 err = json.NewDecoder(hresp.Body).Decode(&resp) 377 if err != nil { 378 t.Fatalf("could not decode response: %v", err) 379 } 380 381 var got []string 382 for _, f := range resp.Content { 383 got = append(got, f.Path) 384 } 385 386 sort.Strings(got) 387 sort.Strings(content) 388 389 if !reflect.DeepEqual(got, content) { 390 t.Fatalf("invalid dirent content: (req=%#v)\ngot= %v\nwant=%v\n", req, got, content) 391 } 392 } 393 394 func TestTree(t *testing.T) { 395 ts := newTestServer() 396 defer ts.Close() 397 398 const uri = "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/small-flat-tree.root" 399 testOpenFile(t, ts, uri, http.StatusOK) 400 defer testCloseFile(t, ts, uri) 401 402 for _, tc := range []struct { 403 req TreeRequest 404 want Tree 405 }{ 406 { 407 req: TreeRequest{ 408 URI: uri, 409 Obj: "tree", 410 }, 411 want: Tree{ 412 Type: "TTree", 413 Name: "tree", 414 Title: "my tree title", 415 Entries: 100, 416 Branches: []Branch{ 417 {Type: "TBranch", Name: "Int32", Leaves: []Leaf{{Type: "int32", Name: "Int32"}}}, 418 {Type: "TBranch", Name: "Int64", Leaves: []Leaf{{Type: "int64", Name: "Int64"}}}, 419 {Type: "TBranch", Name: "UInt32", Leaves: []Leaf{{Type: "uint32", Name: "UInt32"}}}, 420 {Type: "TBranch", Name: "UInt64", Leaves: []Leaf{{Type: "uint64", Name: "UInt64"}}}, 421 {Type: "TBranch", Name: "Float32", Leaves: []Leaf{{Type: "float32", Name: "Float32"}}}, 422 {Type: "TBranch", Name: "Float64", Leaves: []Leaf{{Type: "float64", Name: "Float64"}}}, 423 {Type: "TBranch", Name: "Str", Leaves: []Leaf{{Type: "string", Name: "Str"}}}, 424 {Type: "TBranch", Name: "ArrayInt32", Leaves: []Leaf{{Type: "int32", Name: "ArrayInt32"}}}, 425 {Type: "TBranch", Name: "ArrayInt64", Leaves: []Leaf{{Type: "int64", Name: "ArrayInt64"}}}, 426 {Type: "TBranch", Name: "ArrayUInt32", Leaves: []Leaf{{Type: "uint32", Name: "ArrayInt32"}}}, // FIXME(sbinet): ref-file had a typo (should be ArrayUInt32) 427 {Type: "TBranch", Name: "ArrayUInt64", Leaves: []Leaf{{Type: "uint64", Name: "ArrayInt64"}}}, // FIXME(sbinet): ref-file had a typo (should be ArrayUInt64) 428 {Type: "TBranch", Name: "ArrayFloat32", Leaves: []Leaf{{Type: "float32", Name: "ArrayFloat32"}}}, 429 {Type: "TBranch", Name: "ArrayFloat64", Leaves: []Leaf{{Type: "float64", Name: "ArrayFloat64"}}}, 430 {Type: "TBranch", Name: "N", Leaves: []Leaf{{Type: "int32", Name: "N"}}}, 431 {Type: "TBranch", Name: "SliceInt32", Leaves: []Leaf{{Type: "int32", Name: "SliceInt32"}}}, 432 {Type: "TBranch", Name: "SliceInt64", Leaves: []Leaf{{Type: "int64", Name: "SliceInt64"}}}, 433 {Type: "TBranch", Name: "SliceUInt32", Leaves: []Leaf{{Type: "uint32", Name: "SliceInt32"}}}, // FIXME(sbinet): ref-file had a typo (should be SliceUInt32) 434 {Type: "TBranch", Name: "SliceUInt64", Leaves: []Leaf{{Type: "uint64", Name: "SliceInt64"}}}, // FIXME(sbinet): ref-file had a typo (should be SliceUInt64) 435 {Type: "TBranch", Name: "SliceFloat32", Leaves: []Leaf{{Type: "float32", Name: "SliceFloat32"}}}, 436 {Type: "TBranch", Name: "SliceFloat64", Leaves: []Leaf{{Type: "float64", Name: "SliceFloat64"}}}, 437 }, 438 Leaves: []Leaf{ 439 {Type: "int32", Name: "Int32"}, 440 {Type: "int64", Name: "Int64"}, 441 {Type: "uint32", Name: "UInt32"}, 442 {Type: "uint64", Name: "UInt64"}, 443 {Type: "float32", Name: "Float32"}, 444 {Type: "float64", Name: "Float64"}, 445 {Type: "string", Name: "Str"}, 446 {Type: "int32", Name: "ArrayInt32"}, 447 {Type: "int64", Name: "ArrayInt64"}, 448 {Type: "uint32", Name: "ArrayInt32"}, // FIXME(sbinet): ref-file had a typo (should be ArrayUInt32) 449 {Type: "uint64", Name: "ArrayInt64"}, // FIXME(sbinet): ref-file had a typo (should be ArrayUInt64) 450 {Type: "float32", Name: "ArrayFloat32"}, 451 {Type: "float64", Name: "ArrayFloat64"}, 452 {Type: "int32", Name: "N"}, 453 {Type: "int32", Name: "SliceInt32"}, 454 {Type: "int64", Name: "SliceInt64"}, 455 {Type: "uint32", Name: "SliceInt32"}, // FIXME(sbinet): ref-file had a typo (should be SliceUInt32) 456 {Type: "uint64", Name: "SliceInt64"}, // FIXME(sbinet): ref-file had a typo (should be SliceUInt64) 457 {Type: "float32", Name: "SliceFloat32"}, 458 {Type: "float64", Name: "SliceFloat64"}, 459 }, 460 }, 461 }, 462 } { 463 t.Run(tc.want.Name, func(t *testing.T) { 464 var resp TreeResponse 465 testTree(t, ts, tc.req, &resp) 466 467 if !reflect.DeepEqual(resp.Tree, tc.want) { 468 t.Fatalf("invalid tree:\ngot= %#v\nwant=%#v", resp.Tree, tc.want) 469 } 470 }) 471 } 472 } 473 474 func testTree(t *testing.T, ts *httptest.Server, req TreeRequest, resp *TreeResponse) { 475 t.Helper() 476 477 body := new(bytes.Buffer) 478 err := json.NewEncoder(body).Encode(req) 479 if err != nil { 480 t.Fatalf("could not encode request: %v", err) 481 } 482 483 hreq, err := http.NewRequest(http.MethodPost, ts.URL+"/list-tree", body) 484 if err != nil { 485 t.Fatalf("could not create http request: %v", err) 486 } 487 srv.addCookies(hreq) 488 489 hresp, err := ts.Client().Do(hreq) 490 if err != nil { 491 t.Fatalf("could not post http request: %v", err) 492 } 493 defer hresp.Body.Close() 494 495 if hresp.StatusCode != http.StatusOK { 496 t.Fatalf("could not plot h1: %v", hresp.StatusCode) 497 } 498 499 err = json.NewDecoder(hresp.Body).Decode(resp) 500 if err != nil { 501 t.Fatalf("could not decode response: %v", err) 502 } 503 } 504 505 func TestPlotH1(t *testing.T) { 506 ts := newTestServer() 507 defer ts.Close() 508 509 testOpenFile(t, ts, "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/dirs-6.14.00.root", http.StatusOK) 510 defer testCloseFile(t, ts, "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/dirs-6.14.00.root") 511 512 const uri = "https://codeberg.org/go-hep/hep/raw/branch/main/hbook/rootcnv/testdata/gauss-h1.root" 513 testOpenFile(t, ts, uri, http.StatusOK) 514 defer testCloseFile(t, ts, uri) 515 516 for _, tc := range []struct { 517 req PlotH1Request 518 want string 519 }{ 520 { 521 req: PlotH1Request{ 522 URI: uri, 523 Obj: "h1f", 524 }, 525 want: "testdata/h1f_golden.png", 526 }, 527 { 528 req: PlotH1Request{ 529 URI: "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/dirs-6.14.00.root", 530 Dir: "/dir1/dir11", 531 Obj: "h1", 532 }, 533 want: "testdata/h1_golden.png", 534 }, 535 { 536 req: PlotH1Request{ 537 URI: uri, 538 Obj: "h1f", 539 Options: PlotOptions{ 540 Type: "png", 541 Title: "My Title", 542 X: "X axis [GeV]", 543 Y: "Y axis [A.U]", 544 FillColor: color.RGBA{0, 0, 255, 255}, 545 Line: LineStyle{ 546 Color: color.Black, 547 }, 548 }, 549 }, 550 want: "testdata/h1f_options_golden.png", 551 }, 552 { 553 req: PlotH1Request{ 554 URI: "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/dirs-6.14.00.root", 555 Dir: "/dir1/dir11", 556 Obj: "h1", 557 Options: PlotOptions{ 558 Type: "pdf", 559 }, 560 }, 561 want: "testdata/h1_golden.pdf", 562 }, 563 { 564 req: PlotH1Request{ 565 URI: "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/dirs-6.14.00.root", 566 Dir: "/dir1/dir11", 567 Obj: "h1", 568 Options: PlotOptions{ 569 Type: "svg", 570 }, 571 }, 572 want: "testdata/h1_golden.svg", 573 }, 574 } { 575 t.Run(tc.want, func(t *testing.T) { 576 var resp PlotResponse 577 testPlotH1(t, ts, tc.req, &resp) 578 579 raw, err := base64.StdEncoding.DecodeString(resp.Data) 580 if err != nil { 581 t.Fatal(err) 582 } 583 584 if *cmpimg.GenerateTestData { 585 _ = os.WriteFile(tc.want, raw, 0644) 586 } 587 588 want, err := os.ReadFile(tc.want) 589 if err != nil { 590 t.Fatal(err) 591 } 592 593 typ := tc.req.Options.Type 594 if typ == "" { 595 typ = "png" 596 } 597 if ok, err := cmpimg.EqualApprox(typ, raw, want, 0.1); !ok || err != nil { 598 _ = os.WriteFile(strings.Replace(tc.want, "_golden", "", -1), raw, 0644) 599 fatalf := t.Fatalf 600 if runtime.GOOS == "darwin" { 601 // ignore errors for darwin and mac-silicon 602 fatalf = t.Logf 603 } 604 fatalf("reference files differ: err=%v ok=%v", err, ok) 605 } 606 }) 607 } 608 } 609 610 func testPlotH1(t *testing.T, ts *httptest.Server, req PlotH1Request, resp *PlotResponse) { 611 t.Helper() 612 613 body := new(bytes.Buffer) 614 err := json.NewEncoder(body).Encode(req) 615 if err != nil { 616 t.Fatalf("could not encode request: %v", err) 617 } 618 619 hreq, err := http.NewRequest(http.MethodPost, ts.URL+"/plot-h1", body) 620 if err != nil { 621 t.Fatalf("could not create http request: %v", err) 622 } 623 srv.addCookies(hreq) 624 625 hresp, err := ts.Client().Do(hreq) 626 if err != nil { 627 t.Fatalf("could not post http request: %v", err) 628 } 629 defer hresp.Body.Close() 630 631 if hresp.StatusCode != http.StatusOK { 632 t.Fatalf("could not plot h1: %v", hresp.StatusCode) 633 } 634 635 err = json.NewDecoder(hresp.Body).Decode(resp) 636 if err != nil { 637 t.Fatalf("could not decode response: %v", err) 638 } 639 } 640 641 func TestPlotH2(t *testing.T) { 642 ts := newTestServer() 643 defer ts.Close() 644 645 const uri = "https://codeberg.org/go-hep/hep/raw/branch/main/hbook/rootcnv/testdata/gauss-h2.root" 646 testOpenFile(t, ts, uri, http.StatusOK) 647 defer testCloseFile(t, ts, uri) 648 649 for _, tc := range []struct { 650 req PlotH2Request 651 want string 652 }{ 653 { 654 req: PlotH2Request{ 655 URI: uri, 656 Obj: "h2f", 657 }, 658 want: "testdata/h2f_golden.png", 659 }, 660 { 661 req: PlotH2Request{ 662 URI: uri, 663 Dir: "/", 664 Obj: "h2d", 665 Options: PlotOptions{ 666 Type: "png", 667 }, 668 }, 669 want: "testdata/h2d_golden.png", 670 }, 671 { 672 req: PlotH2Request{ 673 URI: uri, 674 Dir: "/", 675 Obj: "h2d", 676 Options: PlotOptions{ 677 Type: "png", 678 Title: "My Title", 679 X: "X-axis [GeV]", 680 Y: "Y-axis [GeV]", 681 }, 682 }, 683 want: "testdata/h2d_options_golden.png", 684 }, 685 { 686 req: PlotH2Request{ 687 URI: uri, 688 Dir: "/", 689 Obj: "h2d", 690 Options: PlotOptions{ 691 Type: "pdf", 692 }, 693 }, 694 want: "testdata/h2d_golden.pdf", 695 }, 696 { 697 req: PlotH2Request{ 698 URI: uri, 699 Dir: "/", 700 Obj: "h2d", 701 Options: PlotOptions{ 702 Type: "svg", 703 }, 704 }, 705 want: "testdata/h2d_golden.svg", 706 }, 707 } { 708 t.Run(tc.want, func(t *testing.T) { 709 var resp PlotResponse 710 testPlotH2(t, ts, tc.req, &resp) 711 712 raw, err := base64.StdEncoding.DecodeString(resp.Data) 713 if err != nil { 714 t.Fatal(err) 715 } 716 717 if *cmpimg.GenerateTestData { 718 _ = os.WriteFile(tc.want, raw, 0644) 719 } 720 721 want, err := os.ReadFile(tc.want) 722 if err != nil { 723 t.Fatal(err) 724 } 725 726 typ := tc.req.Options.Type 727 if typ == "" { 728 typ = "png" 729 } 730 if ok, err := cmpimg.EqualApprox(typ, raw, want, 0.1); !ok || err != nil { 731 _ = os.WriteFile(strings.Replace(tc.want, "_golden", "", -1), raw, 0644) 732 fatalf := t.Fatalf 733 if runtime.GOOS == "darwin" { 734 // ignore errors for darwin and mac-silicon 735 fatalf = t.Logf 736 } 737 fatalf("reference files differ: err=%v ok=%v", err, ok) 738 } 739 }) 740 } 741 } 742 743 func testPlotH2(t *testing.T, ts *httptest.Server, req PlotH2Request, resp *PlotResponse) { 744 t.Helper() 745 746 body := new(bytes.Buffer) 747 err := json.NewEncoder(body).Encode(req) 748 if err != nil { 749 t.Fatalf("could not encode request: %v", err) 750 } 751 752 hreq, err := http.NewRequest(http.MethodPost, ts.URL+"/plot-h2", body) 753 if err != nil { 754 t.Fatalf("could not create http request: %v", err) 755 } 756 srv.addCookies(hreq) 757 758 hresp, err := ts.Client().Do(hreq) 759 if err != nil { 760 t.Fatalf("could not post http request: %v", err) 761 } 762 defer hresp.Body.Close() 763 764 if hresp.StatusCode != http.StatusOK { 765 t.Fatalf("could not plot h1: %v", hresp.StatusCode) 766 } 767 768 err = json.NewDecoder(hresp.Body).Decode(resp) 769 if err != nil { 770 t.Fatalf("could not decode response: %v", err) 771 } 772 } 773 774 func TestPlotS2(t *testing.T) { 775 ts := newTestServer() 776 defer ts.Close() 777 778 const uri = "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/graphs.root" 779 testOpenFile(t, ts, uri, http.StatusOK) 780 defer testCloseFile(t, ts, uri) 781 782 for _, tc := range []struct { 783 req PlotS2Request 784 want string 785 }{ 786 { 787 req: PlotS2Request{ 788 URI: uri, 789 Obj: "tg", 790 }, 791 want: "testdata/tg_golden.png", 792 }, 793 { 794 req: PlotS2Request{ 795 URI: uri, 796 Dir: "/", 797 Obj: "tge", 798 Options: PlotOptions{ 799 Type: "png", 800 }, 801 }, 802 want: "testdata/tge_golden.png", 803 }, 804 { 805 req: PlotS2Request{ 806 URI: uri, 807 Dir: "/", 808 Obj: "tgae", 809 Options: PlotOptions{ 810 Type: "png", 811 Title: "My Title", 812 X: "X-axis [GeV]", 813 Y: "Y-axis [GeV]", 814 Line: LineStyle{ 815 Color: color.RGBA{B: 255, A: 255}, 816 }, 817 }, 818 }, 819 want: "testdata/tgae_options_golden.png", 820 }, 821 { 822 req: PlotS2Request{ 823 URI: uri, 824 Dir: "/", 825 Obj: "tgae", 826 Options: PlotOptions{ 827 Type: "pdf", 828 }, 829 }, 830 want: "testdata/tgae_golden.pdf", 831 }, 832 { 833 req: PlotS2Request{ 834 URI: uri, 835 Dir: "/", 836 Obj: "tgae", 837 Options: PlotOptions{ 838 Type: "svg", 839 }, 840 }, 841 want: "testdata/tgae_golden.svg", 842 }, 843 } { 844 t.Run(tc.want, func(t *testing.T) { 845 var resp PlotResponse 846 testPlotS2(t, ts, tc.req, &resp) 847 848 raw, err := base64.StdEncoding.DecodeString(resp.Data) 849 if err != nil { 850 t.Fatal(err) 851 } 852 853 if *cmpimg.GenerateTestData { 854 _ = os.WriteFile(tc.want, raw, 0644) 855 } 856 857 want, err := os.ReadFile(tc.want) 858 if err != nil { 859 t.Fatal(err) 860 } 861 862 typ := tc.req.Options.Type 863 if typ == "" { 864 typ = "png" 865 } 866 if ok, err := cmpimg.EqualApprox(typ, raw, want, 0.1); !ok || err != nil { 867 _ = os.WriteFile(strings.Replace(tc.want, "_golden", "", -1), raw, 0644) 868 fatalf := t.Fatalf 869 if runtime.GOOS == "darwin" { 870 // ignore errors for darwin and mac-silicon 871 fatalf = t.Logf 872 } 873 fatalf("reference files differ: err=%v ok=%v", err, ok) 874 } 875 }) 876 } 877 } 878 879 func testPlotS2(t *testing.T, ts *httptest.Server, req PlotS2Request, resp *PlotResponse) { 880 t.Helper() 881 882 body := new(bytes.Buffer) 883 err := json.NewEncoder(body).Encode(req) 884 if err != nil { 885 t.Fatalf("could not encode request: %v", err) 886 } 887 888 hreq, err := http.NewRequest(http.MethodPost, ts.URL+"/plot-s2", body) 889 if err != nil { 890 t.Fatalf("could not create http request: %v", err) 891 } 892 srv.addCookies(hreq) 893 894 hresp, err := ts.Client().Do(hreq) 895 if err != nil { 896 t.Fatalf("could not post http request: %v", err) 897 } 898 defer hresp.Body.Close() 899 900 if hresp.StatusCode != http.StatusOK { 901 t.Fatalf("could not plot h1: %v", hresp.StatusCode) 902 } 903 904 err = json.NewDecoder(hresp.Body).Decode(resp) 905 if err != nil { 906 t.Fatalf("could not decode response: %v", err) 907 } 908 } 909 910 func TestPlotTree(t *testing.T) { 911 ts := newTestServer() 912 defer ts.Close() 913 914 const uri = "https://codeberg.org/go-hep/hep/raw/branch/main/groot/testdata/small-flat-tree.root" 915 testOpenFile(t, ts, uri, http.StatusOK) 916 defer testCloseFile(t, ts, uri) 917 918 for _, tc := range []struct { 919 req PlotTreeRequest 920 want string 921 }{ 922 { 923 req: PlotTreeRequest{ 924 URI: uri, 925 Obj: "tree", 926 Vars: []string{"Int32"}, 927 }, 928 want: "testdata/tree_i32_golden.png", 929 }, 930 { 931 req: PlotTreeRequest{ 932 URI: uri, 933 Dir: "/", 934 Obj: "tree", 935 Vars: []string{"Float64"}, 936 }, 937 want: "testdata/tree_f64_golden.png", 938 }, 939 { 940 req: PlotTreeRequest{ 941 URI: uri, 942 Dir: "/", 943 Obj: "tree", 944 Vars: []string{"ArrayFloat64"}, 945 }, 946 want: "testdata/tree_array_f64_golden.png", 947 }, 948 { 949 req: PlotTreeRequest{ 950 URI: uri, 951 Dir: "/", 952 Obj: "tree", 953 Vars: []string{"SliceFloat64"}, 954 }, 955 want: "testdata/tree_slice_f64_golden.png", 956 }, 957 } { 958 t.Run(tc.want, func(t *testing.T) { 959 var resp PlotResponse 960 testPlotTree(t, ts, tc.req, &resp) 961 962 raw, err := base64.StdEncoding.DecodeString(resp.Data) 963 if err != nil { 964 t.Fatal(err) 965 } 966 967 if *cmpimg.GenerateTestData { 968 _ = os.WriteFile(tc.want, raw, 0644) 969 } 970 971 want, err := os.ReadFile(tc.want) 972 if err != nil { 973 t.Fatal(err) 974 } 975 976 typ := tc.req.Options.Type 977 if typ == "" { 978 typ = "png" 979 } 980 if ok, err := cmpimg.EqualApprox(typ, raw, want, 0.1); !ok || err != nil { 981 _ = os.WriteFile(strings.Replace(tc.want, "_golden", "", -1), raw, 0644) 982 fatalf := t.Fatalf 983 if runtime.GOOS == "darwin" { 984 // ignore errors for darwin and mac-silicon 985 fatalf = t.Logf 986 } 987 fatalf("reference files differ: err=%v ok=%v", err, ok) 988 } 989 }) 990 } 991 } 992 993 func testPlotTree(t *testing.T, ts *httptest.Server, req PlotTreeRequest, resp *PlotResponse) { 994 t.Helper() 995 996 body := new(bytes.Buffer) 997 err := json.NewEncoder(body).Encode(req) 998 if err != nil { 999 t.Fatalf("could not encode request: %v", err) 1000 } 1001 1002 hreq, err := http.NewRequest(http.MethodPost, ts.URL+"/plot-tree", body) 1003 if err != nil { 1004 t.Fatalf("could not create http request: %v", err) 1005 } 1006 srv.addCookies(hreq) 1007 1008 hresp, err := ts.Client().Do(hreq) 1009 if err != nil { 1010 t.Fatalf("could not post http request: %v", err) 1011 } 1012 defer hresp.Body.Close() 1013 1014 if hresp.StatusCode != http.StatusOK { 1015 t.Fatalf("could not plot h1: %v", hresp.StatusCode) 1016 } 1017 1018 err = json.NewDecoder(hresp.Body).Decode(resp) 1019 if err != nil { 1020 t.Fatalf("could not decode response: %v", err) 1021 } 1022 } 1023 1024 func (srv *Server) addCookies(req *http.Request) { 1025 for _, cookie := range srv.cookies { 1026 req.AddCookie(cookie) 1027 } 1028 } 1029 1030 func setupCookie(srv *Server) { 1031 v, err := uuid.GenerateUUID() 1032 if err != nil { 1033 panic(err) 1034 } 1035 cookie := &http.Cookie{ 1036 Name: cookieName, 1037 Value: v, 1038 Expires: time.Now().Add(24 * time.Hour), 1039 } 1040 srv.mu.Lock() 1041 defer srv.mu.Unlock() 1042 srv.sessions[cookie.Value] = NewDB(filepath.Join(srv.dir, cookie.Value)) 1043 srv.cookies[cookie.Value] = cookie 1044 }