go-hep.org/x/hep@v0.38.1/groot/rhist/rootio_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 rhist_test
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  	"reflect"
    14  	"strings"
    15  	"testing"
    16  
    17  	"go-hep.org/x/hep/groot/internal/rtests"
    18  	"go-hep.org/x/hep/groot/rhist"
    19  	"go-hep.org/x/hep/groot/riofs"
    20  	"go-hep.org/x/hep/hbook"
    21  	"go-hep.org/x/hep/hbook/yodacnv"
    22  )
    23  
    24  func TestCreate(t *testing.T) {
    25  
    26  	dir, err := os.MkdirTemp("", "groot-")
    27  	if err != nil {
    28  		t.Fatal(err)
    29  	}
    30  	defer os.RemoveAll(dir)
    31  
    32  	for i, tc := range []struct {
    33  		Name string
    34  		Skip bool
    35  		Want []rtests.ROOTer
    36  		ROOT string
    37  	}{
    38  		{
    39  			Name: "TAxis",
    40  			Want: []rtests.ROOTer{rhist.NewAxis("xaxis")},
    41  			ROOT: "retrieved: [xaxis]\n",
    42  		},
    43  		{
    44  			Name: "TH1I",
    45  			ROOT: "retrieved: [h1i]\n",
    46  			Want: []rtests.ROOTer{
    47  				func() *rhist.H1I {
    48  					h := hbook.NewH1D(100, 0, 100)
    49  					h.Annotation()["name"] = "h1i"
    50  					h.Annotation()["title"] = "my title"
    51  					h.Fill(-1, 1)
    52  					h.Fill(+200, 1)
    53  					h.Fill(1, 1)
    54  					h.Fill(2, 1)
    55  					h.Fill(3, 10)
    56  					return rhist.NewH1IFrom(h)
    57  				}(),
    58  			},
    59  		},
    60  		{
    61  			Name: "TH1F",
    62  			ROOT: "retrieved: [h1f]\n",
    63  			Want: []rtests.ROOTer{
    64  				func() *rhist.H1F {
    65  					h := hbook.NewH1D(100, 0, 100)
    66  					h.Annotation()["name"] = "h1f"
    67  					h.Annotation()["title"] = "my title"
    68  					h.Fill(-1, 1)
    69  					h.Fill(+200, 1)
    70  					h.Fill(1, 1)
    71  					h.Fill(2, 1)
    72  					h.Fill(3, 10)
    73  					return rhist.NewH1FFrom(h)
    74  				}(),
    75  			},
    76  		},
    77  		{
    78  			Name: "TH1D",
    79  			ROOT: "retrieved: [h1d]\n",
    80  			Want: []rtests.ROOTer{
    81  				func() *rhist.H1D {
    82  					h := hbook.NewH1D(100, 0, 100)
    83  					h.Annotation()["name"] = "h1d"
    84  					h.Annotation()["title"] = "my title"
    85  					h.Fill(-1, 1)
    86  					h.Fill(+200, 1)
    87  					h.Fill(1, 1)
    88  					h.Fill(2, 1)
    89  					h.Fill(3, 10)
    90  					return rhist.NewH1DFrom(h)
    91  				}(),
    92  			},
    93  		},
    94  		{
    95  			Name: "TH2I",
    96  			ROOT: "retrieved: [h2i]\n",
    97  			Want: []rtests.ROOTer{
    98  				func() *rhist.H2I {
    99  					h := hbook.NewH2D(100, 0, 100, 50, 0, 50)
   100  					h.Annotation()["name"] = "h2i"
   101  					h.Annotation()["title"] = "my title"
   102  					h.Fill(-1, -1, 1)
   103  					h.Fill(+200, 200, 1)
   104  					h.Fill(1, 1, 1)
   105  					h.Fill(2, 2, 1)
   106  					h.Fill(3, 3, 10)
   107  					return rhist.NewH2IFrom(h)
   108  				}(),
   109  			},
   110  		},
   111  		{
   112  			Name: "TH2F",
   113  			ROOT: "retrieved: [h2f]\n",
   114  			Want: []rtests.ROOTer{
   115  				func() *rhist.H2F {
   116  					h := hbook.NewH2D(100, 0, 100, 50, 0, 50)
   117  					h.Annotation()["name"] = "h2f"
   118  					h.Annotation()["title"] = "my title"
   119  					h.Fill(-1, -1, 1)
   120  					h.Fill(+200, 200, 1)
   121  					h.Fill(1, 1, 1)
   122  					h.Fill(2, 2, 1)
   123  					h.Fill(3, 3, 10)
   124  					return rhist.NewH2FFrom(h)
   125  				}(),
   126  			},
   127  		},
   128  		{
   129  			Name: "TH2D",
   130  			ROOT: "retrieved: [h2d]\n",
   131  			Want: []rtests.ROOTer{
   132  				func() *rhist.H2D {
   133  					h := hbook.NewH2D(100, 0, 100, 50, 0, 50)
   134  					h.Annotation()["name"] = "h2d"
   135  					h.Annotation()["title"] = "my title"
   136  					h.Fill(-1, -1, 1)
   137  					h.Fill(+200, 200, 1)
   138  					h.Fill(1, 1, 1)
   139  					h.Fill(2, 2, 1)
   140  					h.Fill(3, 3, 10)
   141  					return rhist.NewH2DFrom(h)
   142  				}(),
   143  			},
   144  		},
   145  		{
   146  			Name: "TGraph",
   147  			ROOT: "retrieved: [tg]\n",
   148  			Want: []rtests.ROOTer{
   149  				func() rtests.ROOTer {
   150  					hg := hbook.NewS2D(
   151  						hbook.Point2D{X: 1, Y: 1},
   152  						hbook.Point2D{X: 2, Y: 1.5},
   153  						hbook.Point2D{X: -1, Y: +2},
   154  					)
   155  					hg.Annotation()["name"] = "tg"
   156  					hg.Annotation()["title"] = "my title"
   157  					return rhist.NewGraphFrom(hg).(rtests.ROOTer)
   158  				}(),
   159  			},
   160  		},
   161  		{
   162  			Name: "TGraphErrors",
   163  			ROOT: "retrieved: [tge]\n",
   164  			Want: []rtests.ROOTer{
   165  				func() rtests.ROOTer {
   166  					hg := hbook.NewS2D(
   167  						hbook.Point2D{X: 1, Y: 1, ErrX: hbook.Range{Min: 2, Max: 2}, ErrY: hbook.Range{Min: 3, Max: 3}},
   168  						hbook.Point2D{X: 2, Y: 1.5, ErrX: hbook.Range{Min: 2, Max: 2}, ErrY: hbook.Range{Min: 3, Max: 3}},
   169  						hbook.Point2D{X: -1, Y: +2, ErrX: hbook.Range{Min: 2, Max: 2}, ErrY: hbook.Range{Min: 3, Max: 3}},
   170  					)
   171  					hg.Annotation()["name"] = "tge"
   172  					hg.Annotation()["title"] = "my title"
   173  					return rhist.NewGraphErrorsFrom(hg).(rtests.ROOTer)
   174  				}(),
   175  			},
   176  		},
   177  		{
   178  			Name: "TGraphAsymmErrors",
   179  			ROOT: "retrieved: [tgae]\n",
   180  			Want: []rtests.ROOTer{
   181  				func() rtests.ROOTer {
   182  					hg := hbook.NewS2D(
   183  						hbook.Point2D{X: 1, Y: 1, ErrX: hbook.Range{Min: 1, Max: 2}, ErrY: hbook.Range{Min: 3, Max: 4}},
   184  						hbook.Point2D{X: 2, Y: 1.5, ErrX: hbook.Range{Min: 1, Max: 2}, ErrY: hbook.Range{Min: 3, Max: 4}},
   185  						hbook.Point2D{X: -1, Y: +2, ErrX: hbook.Range{Min: 1, Max: 2}, ErrY: hbook.Range{Min: 3, Max: 4}},
   186  					)
   187  					hg.Annotation()["name"] = "tgae"
   188  					hg.Annotation()["title"] = "my title"
   189  					return rhist.NewGraphAsymmErrorsFrom(hg).(rtests.ROOTer)
   190  				}(),
   191  			},
   192  		},
   193  	} {
   194  		fname := filepath.Join(dir, fmt.Sprintf("out-%d.root", i))
   195  		t.Run(tc.Name, func(t *testing.T) {
   196  			if tc.Skip {
   197  				t.Skip()
   198  			}
   199  
   200  			w, err := riofs.Create(fname)
   201  			if err != nil {
   202  				t.Fatal(err)
   203  			}
   204  
   205  			for i := range tc.Want {
   206  				var (
   207  					kname = fmt.Sprintf("key-%s-%02d", tc.Name, i)
   208  					want  = tc.Want[i]
   209  				)
   210  
   211  				err = w.Put(kname, want)
   212  				if err != nil {
   213  					t.Fatal(err)
   214  				}
   215  			}
   216  
   217  			if got, want := len(w.Keys()), len(tc.Want); got != want {
   218  				t.Fatalf("invalid number of keys. got=%d, want=%d", got, want)
   219  			}
   220  
   221  			err = w.Close()
   222  			if err != nil {
   223  				t.Fatalf("error closing file: %v", err)
   224  			}
   225  
   226  			r, err := riofs.Open(fname)
   227  			if err != nil {
   228  				t.Fatal(err)
   229  			}
   230  			defer r.Close()
   231  
   232  			if got, want := len(r.Keys()), len(tc.Want); got != want {
   233  				t.Fatalf("invalid number of keys. got=%d, want=%d", got, want)
   234  			}
   235  
   236  			for i := range tc.Want {
   237  				var (
   238  					kname = fmt.Sprintf("key-%s-%02d", tc.Name, i)
   239  					want  = tc.Want[i]
   240  				)
   241  
   242  				rgot, err := r.Get(kname)
   243  				if err != nil {
   244  					t.Fatal(err)
   245  				}
   246  
   247  				switch rgot := rgot.(type) {
   248  				case yodacnv.Marshaler:
   249  					got, err := rgot.MarshalYODA()
   250  					if err != nil {
   251  						t.Fatalf("could not marshal 'rgot' to YODA: %+v", err)
   252  					}
   253  					want, err := want.(yodacnv.Marshaler).MarshalYODA()
   254  					if err != nil {
   255  						t.Fatalf("could not marshal 'want' to YODA: %+v", err)
   256  					}
   257  					if !bytes.Equal(got, want) {
   258  						t.Fatalf("error reading back value[%d].\ngot:\n%s\nwant:\n%s", i, got, want)
   259  					}
   260  
   261  				default:
   262  					if got := rgot.(rtests.ROOTer); !reflect.DeepEqual(got, want) {
   263  						t.Fatalf("error reading back value[%d].\ngot = %#v\nwant= %#v", i, got, want)
   264  					}
   265  				}
   266  			}
   267  
   268  			err = r.Close()
   269  			if err != nil {
   270  				t.Fatalf("error closing file: %v", err)
   271  			}
   272  
   273  			if !rtests.HasROOT {
   274  				t.Logf("skip test with ROOT/C++")
   275  				return
   276  			}
   277  
   278  			const rootls = `#include <iostream>
   279  #include "TFile.h"
   280  #include "TNamed.h"
   281  
   282  void rootls(const char *fname, const char *kname) {
   283  	auto f = TFile::Open(fname);
   284  	auto o = f->Get<TNamed>(kname);
   285  	if (o == NULL) {
   286  		std:cerr << "could not retrieve [" << kname << "]" << std::endl;
   287  		o->ClassName();
   288  	}
   289  	std::cout << "retrieved: [" << o->GetName() << "]" << std::endl;
   290  }
   291  `
   292  			for i := range tc.Want {
   293  				kname := fmt.Sprintf("key-%s-%02d", tc.Name, i)
   294  
   295  				out, err := rtests.RunCxxROOT("rootls", []byte(rootls), fname, kname)
   296  				if err != nil {
   297  					t.Fatalf("ROOT/C++ could not open file %q:\n%s", fname, string(out))
   298  				}
   299  				if got := stripLine(t, out); got != tc.ROOT {
   300  					t.Fatalf("invalid ROOT/C++ output:\ngot:\n%s\nwant:\n%s", got, tc.ROOT)
   301  				}
   302  			}
   303  		})
   304  	}
   305  }
   306  
   307  func stripLine(t *testing.T, raw []byte) string {
   308  	r := bytes.NewReader(bytes.TrimSpace(raw))
   309  	scan := bufio.NewScanner(r)
   310  	scan.Scan()
   311  
   312  	var o strings.Builder
   313  	for scan.Scan() {
   314  		o.WriteString(scan.Text() + "\n")
   315  	}
   316  	if err := scan.Err(); err != nil {
   317  		t.Fatalf("could not scan text:\n%q\nerr: %+v", raw, err)
   318  	}
   319  
   320  	return o.String()
   321  }