go-hep.org/x/hep@v0.38.1/groot/rtree/writer_test.go (about)

     1  // Copyright ©2019 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 rtree
     6  
     7  import (
     8  	"compress/flate"
     9  	"fmt"
    10  	"math/rand/v2"
    11  	"os"
    12  	"path/filepath"
    13  	"reflect"
    14  	"sync"
    15  	"testing"
    16  
    17  	"go-hep.org/x/hep/groot/internal/rcompress"
    18  	"go-hep.org/x/hep/groot/rbase"
    19  	"go-hep.org/x/hep/groot/riofs"
    20  )
    21  
    22  func TestFlattenArrayType(t *testing.T) {
    23  	for _, tc := range []struct {
    24  		typ   any
    25  		want  any
    26  		shape []int
    27  	}{
    28  		{
    29  			typ:   int32(0),
    30  			want:  int32(0),
    31  			shape: nil,
    32  		},
    33  		{
    34  			typ:   [2]int32{},
    35  			want:  int32(0),
    36  			shape: []int{2},
    37  		},
    38  		{
    39  			typ:   [2][3]int32{},
    40  			want:  int32(0),
    41  			shape: []int{2, 3},
    42  		},
    43  		{
    44  			typ:   [2][3][4]int32{},
    45  			want:  int32(0),
    46  			shape: []int{2, 3, 4},
    47  		},
    48  		{
    49  			typ:   [2][3][4][0]int32{},
    50  			want:  int32(0),
    51  			shape: []int{2, 3, 4, 0},
    52  		},
    53  		{
    54  			typ:   [2][3][4][5]int32{},
    55  			want:  int32(0),
    56  			shape: []int{2, 3, 4, 5},
    57  		},
    58  		{
    59  			typ:   [2][3][4][5][6]int32{},
    60  			want:  int32(0),
    61  			shape: []int{2, 3, 4, 5, 6},
    62  		},
    63  		{
    64  			typ:   [2][3][4][0]struct{}{},
    65  			want:  struct{}{},
    66  			shape: []int{2, 3, 4, 0},
    67  		},
    68  		{
    69  			typ:   [2][3][4][0][]string{},
    70  			want:  []string{},
    71  			shape: []int{2, 3, 4, 0},
    72  		},
    73  		{
    74  			typ:   []string{},
    75  			want:  []string{},
    76  			shape: nil,
    77  		},
    78  	} {
    79  		t.Run(fmt.Sprintf("%T", tc.typ), func(t *testing.T) {
    80  			rt, shape := flattenArrayType(reflect.TypeOf(tc.typ))
    81  			if got, want := rt, reflect.TypeOf(tc.want); got != want {
    82  				t.Fatalf("invalid array element type: got=%v, want=%v", got, want)
    83  			}
    84  
    85  			if got, want := shape, tc.shape; !reflect.DeepEqual(got, want) {
    86  				t.Fatalf("invalid shape: got=%v, want=%v", got, want)
    87  			}
    88  		})
    89  	}
    90  }
    91  
    92  func TestInvalidTreeMerger(t *testing.T) {
    93  	var (
    94  		w   wtree
    95  		src = rbase.NewObjString("foo")
    96  	)
    97  
    98  	err := w.ROOTMerge(src)
    99  	if err == nil {
   100  		t.Fatalf("expected an error")
   101  	}
   102  
   103  	const want = "rtree: can not merge src=*rbase.ObjString into dst=*rtree.wtree"
   104  	if got, want := err.Error(), want; got != want {
   105  		t.Fatalf("invalid ROOTMerge error. got=%q, want=%q", got, want)
   106  	}
   107  }
   108  
   109  func TestConcurrentWrite(t *testing.T) {
   110  	tmp, err := os.MkdirTemp("", "groot-rtree-")
   111  	if err != nil {
   112  		t.Fatal(err)
   113  	}
   114  	defer func() {
   115  		_ = os.RemoveAll(tmp)
   116  	}()
   117  
   118  	const N = 10
   119  	var wg sync.WaitGroup
   120  	wg.Add(N)
   121  
   122  	for i := range N {
   123  		go func(i int) {
   124  			defer wg.Done()
   125  			f, err := riofs.Create(filepath.Join(tmp, fmt.Sprintf("file-%03d.root", i)))
   126  			if err != nil {
   127  				t.Errorf("could not create root file: %+v", err)
   128  				return
   129  			}
   130  			defer f.Close()
   131  
   132  			var (
   133  				evt struct {
   134  					N   int32
   135  					Sli []float64 `groot:"Sli[N]"`
   136  				}
   137  				wvars = WriteVarsFromStruct(&evt)
   138  			)
   139  			w, err := NewWriter(f, "tree", wvars)
   140  			if err != nil {
   141  				t.Errorf("could not create tree writer: %+v", err)
   142  				return
   143  			}
   144  			defer w.Close()
   145  
   146  			rng := rand.New(rand.NewPCG(1234, 1234))
   147  			for range 100 {
   148  				evt.N = rng.Int32N(10) + 1
   149  				evt.Sli = evt.Sli[:0]
   150  				for range int(evt.N) {
   151  					evt.Sli = append(evt.Sli, rng.Float64())
   152  				}
   153  				_, err = w.Write()
   154  				if err != nil {
   155  					t.Errorf("could not write event %d: %+v", i, err)
   156  					return
   157  				}
   158  			}
   159  
   160  			err = w.Close()
   161  			if err != nil {
   162  				t.Errorf("could not close tree writer: %+v", err)
   163  				return
   164  			}
   165  
   166  			err = f.Close()
   167  			if err != nil {
   168  				t.Errorf("could not close root file: %+v", err)
   169  				return
   170  			}
   171  		}(i)
   172  	}
   173  	wg.Wait()
   174  }
   175  
   176  func TestWriteThisStreamers(t *testing.T) {
   177  	tmp, err := os.MkdirTemp("", "groot-rtree-")
   178  	if err != nil {
   179  		t.Fatalf("could not create dir: %v", err)
   180  	}
   181  	defer os.RemoveAll(tmp)
   182  
   183  	fname := filepath.Join(tmp, "streamers.root")
   184  	o, err := riofs.Create(fname)
   185  	if err != nil {
   186  		t.Fatalf("could not create output ROOT file: %+v", err)
   187  	}
   188  	defer o.Close()
   189  
   190  	var evt struct {
   191  		F1 []float64 `groot:"F1"`
   192  		F2 []float64 `groot:"F2"`
   193  		F3 []int64   `groot:"F3"`
   194  		F4 []int64   `groot:"F4"`
   195  	}
   196  
   197  	wvars := WriteVarsFromStruct(&evt)
   198  	tree, err := NewWriter(o, "tree", wvars)
   199  	if err != nil {
   200  		t.Fatalf("could not create output ROOT tree %q: %+v", "tree", err)
   201  	}
   202  
   203  	for i := range 10 {
   204  		evt.F1 = []float64{float64(i)}
   205  		evt.F2 = []float64{float64(i), float64(i)}
   206  		evt.F3 = []int64{int64(i)}
   207  		evt.F4 = []int64{int64(i), int64(i)}
   208  
   209  		_, err = tree.Write()
   210  		if err != nil {
   211  			t.Fatalf("could not write event %d: %+v", i, err)
   212  		}
   213  	}
   214  
   215  	err = tree.Close()
   216  	if err != nil {
   217  		t.Fatalf("could not close tree: %+v", err)
   218  	}
   219  
   220  	err = o.Close()
   221  	if err != nil {
   222  		t.Fatalf("could not close file: %+v", err)
   223  	}
   224  
   225  	f, err := riofs.Open(fname)
   226  	if err != nil {
   227  		t.Fatalf("could not re-open ROOT file: %+v", err)
   228  	}
   229  	defer f.Close()
   230  
   231  	sinfos := make(map[string]int)
   232  	for _, si := range f.StreamerInfos() {
   233  		sinfos[si.Name()]++
   234  	}
   235  
   236  	for _, tc := range []struct {
   237  		name string
   238  		want int
   239  	}{
   240  		{"vector<double>", 1},
   241  		{"vector<int64_t>", 1},
   242  	} {
   243  		got := sinfos[tc.name]
   244  		if got != tc.want {
   245  			t.Errorf("invalid count for %q: got=%d, want=%d", tc.name, got, tc.want)
   246  		}
   247  	}
   248  }
   249  
   250  func TestWriterWithCompression(t *testing.T) {
   251  	tmp, err := os.MkdirTemp("", "groot-rtree-")
   252  	if err != nil {
   253  		t.Fatal(err)
   254  	}
   255  	defer func() {
   256  		_ = os.RemoveAll(tmp)
   257  	}()
   258  
   259  	for _, tc := range []struct {
   260  		wopt WriteOption
   261  		want rcompress.Kind
   262  	}{
   263  		// {WithoutCompression(flate.BestCompression), rcompress.},
   264  		{WithLZ4(flate.BestCompression), rcompress.LZ4},
   265  		{WithLZMA(flate.BestCompression), rcompress.LZMA},
   266  		{WithZlib(flate.BestCompression), rcompress.ZLIB},
   267  		{WithZstd(flate.BestCompression), rcompress.ZSTD},
   268  	} {
   269  		t.Run("alg-"+tc.want.String(), func(t *testing.T) {
   270  			fname := filepath.Join(tmp, "groot-alg-"+tc.want.String())
   271  			f, err := riofs.Create(fname)
   272  			if err != nil {
   273  				t.Fatalf("could not create file %q: %v", fname, err)
   274  			}
   275  			defer f.Close()
   276  
   277  			var (
   278  				evt struct {
   279  					N   int32
   280  					Sli []float64 `groot:"Sli[N]"`
   281  				}
   282  				wvars = WriteVarsFromStruct(&evt)
   283  			)
   284  			w, err := NewWriter(f, "tree", wvars, tc.wopt)
   285  			if err != nil {
   286  				t.Errorf("could not create tree writer: %+v", err)
   287  				return
   288  			}
   289  			defer w.Close()
   290  
   291  			rng := rand.New(rand.NewPCG(1234, 1234))
   292  			for i := range 100 {
   293  				evt.N = rng.Int32N(10) + 1
   294  				evt.Sli = evt.Sli[:0]
   295  				for range int(evt.N) {
   296  					evt.Sli = append(evt.Sli, rng.Float64())
   297  				}
   298  				_, err = w.Write()
   299  				if err != nil {
   300  					t.Errorf("could not write event %d: %+v", i, err)
   301  					return
   302  				}
   303  			}
   304  
   305  			err = w.Close()
   306  			if err != nil {
   307  				t.Errorf("could not close tree writer: %+v", err)
   308  				return
   309  			}
   310  
   311  			err = f.Close()
   312  			if err != nil {
   313  				t.Errorf("could not close root file: %+v", err)
   314  				return
   315  			}
   316  
   317  			{
   318  				f, err := riofs.Open(fname)
   319  				if err != nil {
   320  					t.Fatalf("could not open ROOT file %q: %v", fname, err)
   321  				}
   322  				defer f.Close()
   323  
   324  				tree, err := riofs.Get[Tree](f, "tree")
   325  				if err != nil {
   326  					t.Fatalf("could not open tree: %v", err)
   327  				}
   328  				bname := "Sli"
   329  				b := tree.Branch(bname)
   330  				if b == nil {
   331  					t.Fatalf("could not retrieve branch %q", bname)
   332  				}
   333  				bb, ok := b.(*tbranch)
   334  				if !ok {
   335  					t.Fatalf("unexpected type for branch %q: %T", bname, b)
   336  				}
   337  				xset := rcompress.SettingsFrom(int32(bb.compress))
   338  				if got, want := xset.Alg, tc.want; got != want {
   339  					t.Fatalf("invalid compression algorithm for branch %q: got=%v, want=%v", b.Name(), got, want)
   340  				}
   341  			}
   342  		})
   343  	}
   344  }