go-hep.org/x/hep@v0.38.1/fwk/fwk_test.go (about) 1 // Copyright ©2017 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 fwk_test 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "os" 12 "reflect" 13 "testing" 14 15 "go-hep.org/x/hep/fwk" 16 "go-hep.org/x/hep/fwk/internal/fwktest" 17 "go-hep.org/x/hep/fwk/job" 18 ) 19 20 func newapp(evtmax int64, nprocs int) *job.Job { 21 app := job.NewJob(nil, job.P{ 22 "EvtMax": evtmax, 23 "NProcs": nprocs, 24 "MsgLevel": job.MsgLevel("ERROR"), 25 }) 26 return app 27 } 28 29 func TestSimpleSeqApp(t *testing.T) { 30 31 app := newapp(10, 0) 32 app.Create(job.C{ 33 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task1", 34 Name: "t0", 35 Props: job.P{ 36 "Ints1": "t0-ints1", 37 "Ints2": "t0-ints2", 38 }, 39 }) 40 41 app.Create(job.C{ 42 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task1", 43 Name: "t1", 44 Props: job.P{ 45 "Ints1": "t1-ints1", 46 "Ints2": "t2-ints2", 47 }, 48 }) 49 50 app.Create(job.C{ 51 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task2", 52 Name: "t2", 53 Props: job.P{ 54 "Input": "t1-ints1", 55 "Output": "t1-ints1-massaged", 56 }, 57 }) 58 59 app.Create(job.C{ 60 Type: "go-hep.org/x/hep/fwk/internal/fwktest.svc1", 61 Name: "svc1", 62 }) 63 64 app.Run() 65 } 66 67 func TestSimpleConcApp(t *testing.T) { 68 69 for _, nprocs := range []int{1, 2, 4, 8} { 70 app := newapp(10, nprocs) 71 app.Create(job.C{ 72 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task1", 73 Name: "t0", 74 Props: job.P{ 75 "Ints1": "t0-ints1", 76 "Ints2": "t0-ints2", 77 }, 78 }) 79 80 app.Create(job.C{ 81 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task1", 82 Name: "t1", 83 Props: job.P{ 84 "Ints1": "t1-ints1", 85 "Ints2": "t2-ints2", 86 }, 87 }) 88 89 app.Create(job.C{ 90 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task2", 91 Name: "t2", 92 Props: job.P{ 93 "Input": "t1-ints1", 94 "Output": "t1-ints1-massaged", 95 }, 96 }) 97 app.Run() 98 } 99 } 100 101 func TestDuplicateOutputPort(t *testing.T) { 102 app := newapp(1, 1) 103 app.Create(job.C{ 104 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task1", 105 Name: "t0", 106 Props: job.P{ 107 "Ints1": "t0-ints1", 108 "Ints2": "t0-ints2", 109 }, 110 }) 111 112 app.Create(job.C{ 113 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task1", 114 Name: "t1", 115 Props: job.P{ 116 "Ints1": "t0-ints1", 117 "Ints2": "t0-ints2", 118 }, 119 }) 120 err := app.App().Run() 121 if err == nil { 122 t.Fatalf("expected an error\n") 123 } 124 want := fmt.Errorf(`fwk.DeclOutPort: component [t0] already declared out-port with name [t0-ints1 (type=int64)]. 125 fwk.DeclOutPort: component [t1] is trying to add a duplicate out-port [t0-ints1 (type=int64)]`) 126 if got, want := err.Error(), want.Error(); got != want { 127 t.Fatalf("invalid error.\ngot= %v\nwant=%v", got, want) 128 } 129 } 130 131 func TestMissingInputPort(t *testing.T) { 132 app := newapp(1, 1) 133 app.Create(job.C{ 134 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task1", 135 Name: "t1", 136 Props: job.P{ 137 "Ints1": "t1-ints1", 138 "Ints2": "t1-ints2", 139 }, 140 }) 141 142 app.Create(job.C{ 143 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task2", 144 Name: "t2", 145 Props: job.P{ 146 "Input": "t1-ints1--NOT-THERE", 147 "Output": "t2-ints2", 148 }, 149 }) 150 151 err := app.App().Run() 152 if err == nil { 153 t.Fatalf("expected an error\n") 154 } 155 want := fmt.Errorf("dataflow: component [%s] declared port [t1-ints1--NOT-THERE] as input but NO KNOWN producer", "t2") 156 if got, want := err.Error(), want.Error(); got != want { 157 t.Fatalf("invalid error.\ngot= %v\nwant=%v", got, want) 158 } 159 } 160 161 func TestMismatchPortTypes(t *testing.T) { 162 app := newapp(1, 1) 163 app.Create(job.C{ 164 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task1", 165 Name: "t1", 166 Props: job.P{ 167 "Ints1": "t1-ints1", 168 "Ints2": "t1-ints2", 169 }, 170 }) 171 172 app.Create(job.C{ 173 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task2", 174 Name: "t2", 175 Props: job.P{ 176 "Input": "t1-ints1", 177 "Output": "data", 178 }, 179 }) 180 181 app.Create(job.C{ 182 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task4", 183 Name: "t4", 184 Props: job.P{ 185 "Input": "data", 186 "Output": "out-data", 187 }, 188 }) 189 190 err := app.App().Run() 191 if err == nil { 192 t.Fatalf("expected an error\n") 193 } 194 want := fmt.Errorf(`fwk.DeclInPort: detected type inconsistency for port [data]: 195 component=%[1]q port=out type=int64 196 component=%[2]q port=in type=float64 197 `, 198 "t2", 199 "t4", 200 ) 201 if got, want := err.Error(), want.Error(); got != want { 202 t.Fatalf("invalid error.\ngot= %v\nwant=%v", got, want) 203 } 204 } 205 206 func TestPortsCycles(t *testing.T) { 207 app := newapp(1, 1) 208 app.Create(job.C{ 209 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task2", 210 Name: "t1-cycle", 211 Props: job.P{ 212 "Input": "input", 213 "Output": "data-1", 214 }, 215 }) 216 217 app.Create(job.C{ 218 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task2", 219 Name: "t2", 220 Props: job.P{ 221 "Input": "data-1", 222 "Output": "data-2", 223 }, 224 }) 225 226 app.Create(job.C{ 227 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task2", 228 Name: "t3", 229 Props: job.P{ 230 "Input": "data-2", 231 "Output": "input", 232 }, 233 }) 234 235 err := app.App().Run() 236 if err == nil { 237 t.Fatalf("expected an error\n") 238 } 239 want := fmt.Errorf("dataflow: cycle detected: 1") 240 if got, want := err.Error(), want.Error(); got != want { 241 t.Fatalf("invalid error.\ngot= %v\nwant=%v", got, want) 242 } 243 } 244 245 func getsumsq(n int64) int64 { 246 sum := int64(0) 247 for i := int64(0); i < n; i++ { 248 sum += i * i 249 } 250 return sum 251 } 252 253 func newTestReader(max int) io.Reader { 254 buf := new(bytes.Buffer) 255 for i := range max { 256 fmt.Fprintf(buf, "%d\n", int64(i)) 257 } 258 return buf 259 } 260 261 func TestInputStream(t *testing.T) { 262 const max = 1000 263 for _, evtmax := range []int64{0, 1, 10, 100, -1} { 264 for _, nprocs := range []int{0, 1, 2, 4, 8, -1} { 265 nmax := evtmax 266 if nmax < 0 { 267 nmax = max 268 } 269 270 app := newapp(evtmax, nprocs) 271 272 app.Create(job.C{ 273 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task2", 274 Name: "t2", 275 Props: job.P{ 276 "Input": "t1-ints1", 277 "Output": "t1-ints1-massaged", 278 }, 279 }) 280 281 // put input-stream after 't2', to test dataflow re-ordering 282 app.Create(job.C{ 283 Type: "go-hep.org/x/hep/fwk.InputStream", 284 Name: "input", 285 Props: job.P{ 286 "Ports": []fwk.Port{ 287 { 288 Name: "t1-ints1", 289 Type: reflect.TypeOf(int64(1)), 290 }, 291 }, 292 "Streamer": &fwktest.InputStream{ 293 R: newTestReader(max), 294 }, 295 }, 296 }) 297 298 // check we read the expected amount values 299 app.Create(job.C{ 300 Type: "go-hep.org/x/hep/fwk/internal/fwktest.reducer", 301 Name: "reducer", 302 Props: job.P{ 303 "Input": "t1-ints1-massaged", 304 "Sum": getsumsq(nmax), 305 }, 306 }) 307 308 err := app.App().Run() 309 if err != nil { 310 t.Errorf("error (evtmax=%d nprocs=%d): %v\n", evtmax, nprocs, err) 311 } 312 } 313 } 314 } 315 316 func TestOutputStream(t *testing.T) { 317 const max = 1000 318 for _, evtmax := range []int64{0, 1, 10, 100, -1} { 319 for _, nprocs := range []int{0, 1, 2, 4, 8, -1} { 320 nmax := evtmax 321 if nmax < 0 { 322 nmax = max 323 } 324 325 app := newapp(evtmax, nprocs) 326 327 fname := fmt.Sprintf("test-output-stream_%d_%d.txt", evtmax, nprocs) 328 w, err := os.Create(fname) 329 if err != nil { 330 t.Fatalf("could not create output file [%s]: %v\n", fname, err) 331 } 332 defer w.Close() 333 334 // put output-stream before 'reducer', to test dataflow re-ordering 335 app.Create(job.C{ 336 Type: "go-hep.org/x/hep/fwk.OutputStream", 337 Name: "output", 338 Props: job.P{ 339 "Ports": []fwk.Port{ 340 { 341 Name: "t1-ints1-massaged", 342 Type: reflect.TypeOf(int64(1)), 343 }, 344 }, 345 "Streamer": &fwktest.OutputStream{ 346 W: w, 347 }, 348 }, 349 }) 350 351 app.Create(job.C{ 352 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task2", 353 Name: "t2", 354 Props: job.P{ 355 "Input": "t1-ints1", 356 "Output": "t1-ints1-massaged", 357 }, 358 }) 359 360 // check we read the expected amount values 361 app.Create(job.C{ 362 Type: "go-hep.org/x/hep/fwk/internal/fwktest.reducer", 363 Name: "reducer", 364 Props: job.P{ 365 "Input": "t1-ints1-massaged", 366 "Sum": getsumsq(nmax), 367 }, 368 }) 369 370 // put input-stream after 't2', to test dataflow re-ordering 371 app.Create(job.C{ 372 Type: "go-hep.org/x/hep/fwk.InputStream", 373 Name: "input", 374 Props: job.P{ 375 "Ports": []fwk.Port{ 376 { 377 Name: "t1-ints1", 378 Type: reflect.TypeOf(int64(1)), 379 }, 380 }, 381 "Streamer": &fwktest.InputStream{ 382 R: newTestReader(max), 383 }, 384 }, 385 }) 386 err = app.App().Run() 387 if err != nil { 388 t.Errorf("error (evtmax=%d nprocs=%d): %v\n", evtmax, nprocs, err) 389 } 390 391 err = w.Close() 392 if err != nil { 393 t.Fatalf("could not close file [%s]: %v\n", fname, err) 394 } 395 w, err = os.Open(fname) 396 if err != nil { 397 t.Fatalf("could not open file [%s]: %v\n", fname, err) 398 } 399 defer w.Close() 400 exp := getsumsq(nmax) 401 sum := int64(0) 402 for { 403 var val int64 404 _, err = fmt.Fscanf(w, "%d\n", &val) 405 if err != nil { 406 break 407 } 408 sum += val 409 } 410 if err == io.EOF { 411 err = nil 412 } 413 if err != nil { 414 t.Fatalf("problem scanning output file [%s]: %v\n", fname, err) 415 } 416 if sum != exp { 417 t.Fatalf("problem validating file [%s]: expected sum=%d. got=%d\n", 418 fname, exp, sum, 419 ) 420 } 421 os.Remove(fname) 422 } 423 } 424 } 425 426 func Benchmark___SeqApp(b *testing.B) { 427 app := newapp(100, 0) 428 app.Create(job.C{ 429 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task1", 430 Name: "t0", 431 Props: job.P{ 432 "Ints1": "t0-ints1", 433 "Ints2": "t0-ints2", 434 }, 435 }) 436 437 app.Create(job.C{ 438 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task1", 439 Name: "t1", 440 Props: job.P{ 441 "Ints1": "t0", 442 "Ints2": "t2-ints2", 443 }, 444 }) 445 446 input := "t0" 447 for i := range 100 { 448 name := fmt.Sprintf("tx-%d", i) 449 out := fmt.Sprintf("tx-%d", i) 450 app.Create(job.C{ 451 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task2", 452 Name: name, 453 Props: job.P{ 454 "Input": input, 455 "Output": out, 456 }, 457 }) 458 input = out 459 } 460 461 ui := app.App().Scripter() 462 err := ui.Configure() 463 if err != nil { 464 b.Fatalf("error: %v\n", err) 465 } 466 467 err = ui.Start() 468 if err != nil { 469 b.Fatalf("error: %v\n", err) 470 } 471 472 b.ResetTimer() 473 for i := 0; i < b.N; i++ { 474 err = ui.Run(-1) 475 if err != nil && err != io.EOF { 476 b.Fatalf("error: %v\n", err) 477 } 478 } 479 } 480 481 func Benchmark__ConcApp(b *testing.B) { 482 app := newapp(100, 4) 483 app.Create(job.C{ 484 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task1", 485 Name: "t0", 486 Props: job.P{ 487 "Ints1": "t0-ints1", 488 "Ints2": "t0-ints2", 489 }, 490 }) 491 492 app.Create(job.C{ 493 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task1", 494 Name: "t1", 495 Props: job.P{ 496 "Ints1": "t0", 497 "Ints2": "t2-ints2", 498 }, 499 }) 500 501 input := "t0" 502 for i := range 100 { 503 name := fmt.Sprintf("tx-%d", i) 504 out := fmt.Sprintf("tx-%d", i) 505 app.Create(job.C{ 506 Type: "go-hep.org/x/hep/fwk/internal/fwktest.task2", 507 Name: name, 508 Props: job.P{ 509 "Input": input, 510 "Output": out, 511 }, 512 }) 513 input = out 514 } 515 516 ui := app.App().Scripter() 517 err := ui.Configure() 518 if err != nil { 519 b.Fatalf("error: %v\n", err) 520 } 521 522 err = ui.Start() 523 if err != nil { 524 b.Fatalf("error: %v\n", err) 525 } 526 527 b.ResetTimer() 528 for i := 0; i < b.N; i++ { 529 err = ui.Run(-1) 530 if err != nil && err != io.EOF { 531 b.Fatalf("error: %v\n", err) 532 } 533 } 534 }