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  }