github.com/grafana/pyroscope@v1.18.0/pkg/og/structs/flamebearer/convert/convert_test.go (about)

     1  package convert
     2  
     3  import (
     4  	"io/ioutil"
     5  	"reflect"
     6  
     7  	"google.golang.org/protobuf/proto"
     8  
     9  	. "github.com/onsi/ginkgo/v2"
    10  	. "github.com/onsi/gomega"
    11  
    12  	profilev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1"
    13  )
    14  
    15  var _ = Describe("Server", func() {
    16  	Describe("Detecting format", func() {
    17  		Context("with a valid pprof type", func() {
    18  			When("there's only type", func() {
    19  				var m ProfileFile
    20  
    21  				BeforeEach(func() {
    22  					m = ProfileFile{
    23  						Type: "pprof",
    24  					}
    25  				})
    26  				It("should return pprof as type is be enough to detect the type", func() {
    27  					// We want to compare functions, which is not ideal.
    28  					expected := reflect.ValueOf(PprofToProfile).Pointer()
    29  					f, err := converter(m)
    30  					Expect(err).To(BeNil())
    31  					Expect(f).ToNot(BeNil())
    32  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
    33  				})
    34  			})
    35  			When("there's pprof type and json filename", func() {
    36  				var m ProfileFile
    37  
    38  				BeforeEach(func() {
    39  					m = ProfileFile{
    40  						Name: "profile.json",
    41  						Type: "pprof",
    42  					}
    43  				})
    44  				It("should return pprof as type takes precedence over filename", func() {
    45  					// We want to compare functions, which is not ideal.
    46  					expected := reflect.ValueOf(PprofToProfile).Pointer()
    47  					f, err := converter(m)
    48  					Expect(err).To(BeNil())
    49  					Expect(f).ToNot(BeNil())
    50  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
    51  				})
    52  			})
    53  
    54  			When("there's pprof type and json profile contents", func() {
    55  				var m ProfileFile
    56  
    57  				BeforeEach(func() {
    58  					m = ProfileFile{
    59  						Data: []byte(`{"flamebearer":""}`),
    60  						Type: "pprof",
    61  					}
    62  				})
    63  				It("should return pprof as type takes precedence over profile contents", func() {
    64  					// We want to compare functions, which is not ideal.
    65  					expected := reflect.ValueOf(PprofToProfile).Pointer()
    66  					f, err := converter(m)
    67  					Expect(err).To(BeNil())
    68  					Expect(f).ToNot(BeNil())
    69  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
    70  				})
    71  			})
    72  		})
    73  
    74  		Context("with no (valid) type and a valid pprof filename", func() {
    75  			When("there's pprof filename and json profile contents", func() {
    76  				var m ProfileFile
    77  				BeforeEach(func() {
    78  					m = ProfileFile{
    79  						Name: "profile.pprof",
    80  						Data: []byte(`{"flamebearer":""}`),
    81  					}
    82  				})
    83  
    84  				It("should return pprof as filename takes precedence over profile contents", func() {
    85  					// We want to compare functions, which is not ideal.
    86  					expected := reflect.ValueOf(PprofToProfile).Pointer()
    87  					f, err := converter(m)
    88  					Expect(err).To(BeNil())
    89  					Expect(f).ToNot(BeNil())
    90  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
    91  				})
    92  			})
    93  			When("there's pprof filename and an unsupported type", func() {
    94  				var m ProfileFile
    95  				BeforeEach(func() {
    96  					m = ProfileFile{
    97  						Name: "profile.pprof",
    98  						Type: "unsupported",
    99  					}
   100  				})
   101  
   102  				It("should return pprof as unsupported type is ignored", func() {
   103  					// We want to compare functions, which is not ideal.
   104  					expected := reflect.ValueOf(PprofToProfile).Pointer()
   105  					f, err := converter(m)
   106  					Expect(err).To(BeNil())
   107  					Expect(f).ToNot(BeNil())
   108  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   109  				})
   110  			})
   111  		})
   112  
   113  		Context("with no (valid) type and filename, a valid pprof profile", func() {
   114  			When("there's a profile with uncompressed pprof content", func() {
   115  				var m ProfileFile
   116  
   117  				BeforeEach(func() {
   118  					m = ProfileFile{
   119  						Data: []byte{0x0a, 0x04},
   120  					}
   121  				})
   122  
   123  				It("should return pprof", func() {
   124  					// We want to compare functions, which is not ideal.
   125  					expected := reflect.ValueOf(PprofToProfile).Pointer()
   126  					f, err := converter(m)
   127  					Expect(err).To(BeNil())
   128  					Expect(f).ToNot(BeNil())
   129  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   130  				})
   131  			})
   132  
   133  			When("there's a profile with compressed pprof content", func() {
   134  				var m ProfileFile
   135  
   136  				BeforeEach(func() {
   137  					m = ProfileFile{
   138  						Data: []byte{0x1f, 0x8b},
   139  					}
   140  				})
   141  
   142  				It("should return pprof", func() {
   143  					// We want to compare functions, which is not ideal.
   144  					expected := reflect.ValueOf(PprofToProfile).Pointer()
   145  					f, err := converter(m)
   146  					Expect(err).To(BeNil())
   147  					Expect(f).ToNot(BeNil())
   148  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   149  				})
   150  			})
   151  
   152  			When("there's a profile with compressed pprof content and an unsupported type", func() {
   153  				var m ProfileFile
   154  
   155  				BeforeEach(func() {
   156  					m = ProfileFile{
   157  						Data: []byte{0x1f, 0x8b},
   158  						Type: "unsupported",
   159  					}
   160  				})
   161  
   162  				It("should return pprof as unsupported types are ignored", func() {
   163  					// We want to compare functions, which is not ideal.
   164  					expected := reflect.ValueOf(PprofToProfile).Pointer()
   165  					f, err := converter(m)
   166  					Expect(err).To(BeNil())
   167  					Expect(f).ToNot(BeNil())
   168  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   169  				})
   170  			})
   171  
   172  			When("there's a profile with compressed pprof content and unsupported filename", func() {
   173  				var m ProfileFile
   174  
   175  				BeforeEach(func() {
   176  					m = ProfileFile{
   177  						Name: "profile.unsupported",
   178  						Data: []byte{0x1f, 0x8b},
   179  					}
   180  				})
   181  
   182  				It("should return pprof as unsupported filenames are ignored", func() {
   183  					// We want to compare functions, which is not ideal.
   184  					expected := reflect.ValueOf(PprofToProfile).Pointer()
   185  					f, err := converter(m)
   186  					Expect(err).To(BeNil())
   187  					Expect(f).ToNot(BeNil())
   188  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   189  				})
   190  			})
   191  		})
   192  
   193  		Context("with a valid json type", func() {
   194  			When("there's only type", func() {
   195  				var m ProfileFile
   196  
   197  				BeforeEach(func() {
   198  					m = ProfileFile{
   199  						Type: "json",
   200  					}
   201  				})
   202  				It("should return json as type is be enough to detect the type", func() {
   203  					// We want to compare functions, which is not ideal.
   204  					expected := reflect.ValueOf(JSONToProfile).Pointer()
   205  					f, err := converter(m)
   206  					Expect(err).To(BeNil())
   207  					Expect(f).ToNot(BeNil())
   208  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   209  				})
   210  			})
   211  			When("there's json type and pprof filename", func() {
   212  				var m ProfileFile
   213  
   214  				BeforeEach(func() {
   215  					m = ProfileFile{
   216  						Name: "profile.pprof",
   217  						Type: "json",
   218  					}
   219  				})
   220  				It("should return json as type takes precedence over filename", func() {
   221  					// We want to compare functions, which is not ideal.
   222  					expected := reflect.ValueOf(JSONToProfile).Pointer()
   223  					f, err := converter(m)
   224  					Expect(err).To(BeNil())
   225  					Expect(f).ToNot(BeNil())
   226  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   227  				})
   228  			})
   229  
   230  			When("there's json type and pprof profile contents", func() {
   231  				var m ProfileFile
   232  
   233  				BeforeEach(func() {
   234  					m = ProfileFile{
   235  						Data: []byte{0x1f, 0x8b},
   236  						Type: "json",
   237  					}
   238  				})
   239  				It("should return json as type takes precedence over profile contents", func() {
   240  					// We want to compare functions, which is not ideal.
   241  					expected := reflect.ValueOf(JSONToProfile).Pointer()
   242  					f, err := converter(m)
   243  					Expect(err).To(BeNil())
   244  					Expect(f).ToNot(BeNil())
   245  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   246  				})
   247  			})
   248  		})
   249  
   250  		Context("with no (valid) type and a valid json filename", func() {
   251  			When("there's json filename and pprof profile contents", func() {
   252  				var m ProfileFile
   253  				BeforeEach(func() {
   254  					m = ProfileFile{
   255  						Name: "profile.json",
   256  						Data: []byte{0x1f, 0x8b},
   257  					}
   258  				})
   259  
   260  				It("should return json as filename takes precedence over profile contents", func() {
   261  					// We want to compare functions, which is not ideal.
   262  					expected := reflect.ValueOf(JSONToProfile).Pointer()
   263  					f, err := converter(m)
   264  					Expect(err).To(BeNil())
   265  					Expect(f).ToNot(BeNil())
   266  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   267  				})
   268  			})
   269  			When("there's json filename and an unsupported type", func() {
   270  				var m ProfileFile
   271  				BeforeEach(func() {
   272  					m = ProfileFile{
   273  						Name: "profile.json",
   274  						Type: "unsupported",
   275  					}
   276  				})
   277  
   278  				It("should return json as unsupported type is ignored", func() {
   279  					// We want to compare functions, which is not ideal.
   280  					expected := reflect.ValueOf(JSONToProfile).Pointer()
   281  					f, err := converter(m)
   282  					Expect(err).To(BeNil())
   283  					Expect(f).ToNot(BeNil())
   284  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   285  				})
   286  			})
   287  		})
   288  
   289  		Context("with no (valid) type and filename, a valid json profile", func() {
   290  			When("there's a profile with json content", func() {
   291  				var m ProfileFile
   292  
   293  				BeforeEach(func() {
   294  					m = ProfileFile{
   295  						Data: []byte(`{"flamebearer":""}`),
   296  					}
   297  				})
   298  
   299  				It("should return json", func() {
   300  					// We want to compare functions, which is not ideal.
   301  					expected := reflect.ValueOf(JSONToProfile).Pointer()
   302  					f, err := converter(m)
   303  					Expect(err).To(BeNil())
   304  					Expect(f).ToNot(BeNil())
   305  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   306  				})
   307  			})
   308  
   309  			When("there's a profile with json content and an unsupported type", func() {
   310  				var m ProfileFile
   311  
   312  				BeforeEach(func() {
   313  					m = ProfileFile{
   314  						Data: []byte(`{"flamebearer":""}`),
   315  						Type: "unsupported",
   316  					}
   317  				})
   318  
   319  				It("should return json as unsupported types are ignored", func() {
   320  					// We want to compare functions, which is not ideal.
   321  					expected := reflect.ValueOf(JSONToProfile).Pointer()
   322  					f, err := converter(m)
   323  					Expect(err).To(BeNil())
   324  					Expect(f).ToNot(BeNil())
   325  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   326  				})
   327  			})
   328  
   329  			When("there's a profile with json content and unsupported filename", func() {
   330  				var m ProfileFile
   331  
   332  				BeforeEach(func() {
   333  					m = ProfileFile{
   334  						Name: "profile.unsupported",
   335  						Data: []byte(`{"flamebearer":""}`),
   336  					}
   337  				})
   338  
   339  				It("should return json as unsupported filenames are ignored", func() {
   340  					// We want to compare functions, which is not ideal.
   341  					expected := reflect.ValueOf(JSONToProfile).Pointer()
   342  					f, err := converter(m)
   343  					Expect(err).To(BeNil())
   344  					Expect(f).ToNot(BeNil())
   345  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   346  				})
   347  			})
   348  		})
   349  
   350  		Context("with a valid collapsed type", func() {
   351  			When("there's only type", func() {
   352  				var m ProfileFile
   353  
   354  				BeforeEach(func() {
   355  					m = ProfileFile{
   356  						Type: "collapsed",
   357  					}
   358  				})
   359  				It("should return collapsed as type is be enough to detect the type", func() {
   360  					// We want to compare functions, which is not ideal.
   361  					expected := reflect.ValueOf(CollapsedToProfile).Pointer()
   362  					f, err := converter(m)
   363  					Expect(err).To(BeNil())
   364  					Expect(f).ToNot(BeNil())
   365  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   366  				})
   367  			})
   368  			When("there's collapsed type and pprof filename", func() {
   369  				var m ProfileFile
   370  
   371  				BeforeEach(func() {
   372  					m = ProfileFile{
   373  						Name: "profile.pprof",
   374  						Type: "collapsed",
   375  					}
   376  				})
   377  				It("should return collapsed as type takes precedence over filename", func() {
   378  					// We want to compare functions, which is not ideal.
   379  					expected := reflect.ValueOf(CollapsedToProfile).Pointer()
   380  					f, err := converter(m)
   381  					Expect(err).To(BeNil())
   382  					Expect(f).ToNot(BeNil())
   383  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   384  				})
   385  			})
   386  
   387  			When("there's json type and pprof profile contents", func() {
   388  				var m ProfileFile
   389  
   390  				BeforeEach(func() {
   391  					m = ProfileFile{
   392  						Data: []byte{0x1f, 0x8b},
   393  						Type: "collapsed",
   394  					}
   395  				})
   396  				It("should return collapsed as type takes precedence over profile contents", func() {
   397  					// We want to compare functions, which is not ideal.
   398  					expected := reflect.ValueOf(CollapsedToProfile).Pointer()
   399  					f, err := converter(m)
   400  					Expect(err).To(BeNil())
   401  					Expect(f).ToNot(BeNil())
   402  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   403  				})
   404  			})
   405  		})
   406  
   407  		Context("with no (valid) type and a valid collapsed filename", func() {
   408  			When("there's collapsed filename and pprof profile contents", func() {
   409  				var m ProfileFile
   410  				BeforeEach(func() {
   411  					m = ProfileFile{
   412  						Name: "profile.collapsed",
   413  						Data: []byte{0x1f, 0x8b},
   414  					}
   415  				})
   416  
   417  				It("should return collapsed as filename takes precedence over profile contents", func() {
   418  					// We want to compare functions, which is not ideal.
   419  					expected := reflect.ValueOf(CollapsedToProfile).Pointer()
   420  					f, err := converter(m)
   421  					Expect(err).To(BeNil())
   422  					Expect(f).ToNot(BeNil())
   423  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   424  				})
   425  			})
   426  			When("there's collapsed filename and an unsupported type", func() {
   427  				var m ProfileFile
   428  				BeforeEach(func() {
   429  					m = ProfileFile{
   430  						Name: "profile.collapsed",
   431  						Type: "unsupported",
   432  					}
   433  				})
   434  
   435  				It("should return collapsed as unsupported type is ignored", func() {
   436  					// We want to compare functions, which is not ideal.
   437  					expected := reflect.ValueOf(CollapsedToProfile).Pointer()
   438  					f, err := converter(m)
   439  					Expect(err).To(BeNil())
   440  					Expect(f).ToNot(BeNil())
   441  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   442  				})
   443  			})
   444  
   445  			When("there's collapsed text filename and an unsupported type", func() {
   446  				var m ProfileFile
   447  				BeforeEach(func() {
   448  					m = ProfileFile{
   449  						Name: "profile.collapsed.txt",
   450  						Type: "unsupported",
   451  					}
   452  				})
   453  
   454  				It("should return collapsed as unsupported type is ignored", func() {
   455  					// We want to compare functions, which is not ideal.
   456  					expected := reflect.ValueOf(CollapsedToProfile).Pointer()
   457  					f, err := converter(m)
   458  					Expect(err).To(BeNil())
   459  					Expect(f).ToNot(BeNil())
   460  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   461  				})
   462  			})
   463  		})
   464  
   465  		Context("with no (valid) type and filename, a valid collapsed profile", func() {
   466  			When("there's a profile with collapsed content", func() {
   467  				var m ProfileFile
   468  
   469  				BeforeEach(func() {
   470  					m = ProfileFile{
   471  						Data: []byte("fn1 1\nfn2 2"),
   472  					}
   473  				})
   474  
   475  				It("should return collapsed", func() {
   476  					// We want to compare functions, which is not ideal.
   477  					expected := reflect.ValueOf(CollapsedToProfile).Pointer()
   478  					f, err := converter(m)
   479  					Expect(err).To(BeNil())
   480  					Expect(f).ToNot(BeNil())
   481  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   482  				})
   483  			})
   484  
   485  			When("there's a profile with collapsed content and an unsupported type", func() {
   486  				var m ProfileFile
   487  
   488  				BeforeEach(func() {
   489  					m = ProfileFile{
   490  						Data: []byte("fn1 1\nfn2 2"),
   491  						Type: "unsupported",
   492  					}
   493  				})
   494  
   495  				It("should return collapsed as unsupported types are ignored", func() {
   496  					// We want to compare functions, which is not ideal.
   497  					expected := reflect.ValueOf(CollapsedToProfile).Pointer()
   498  					f, err := converter(m)
   499  					Expect(err).To(BeNil())
   500  					Expect(f).ToNot(BeNil())
   501  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   502  				})
   503  			})
   504  
   505  			When("there's a profile with collapsed content and unsupported filename", func() {
   506  				var m ProfileFile
   507  
   508  				BeforeEach(func() {
   509  					m = ProfileFile{
   510  						Name: "profile.unsupported",
   511  						Data: []byte("fn1 1\nfn2 2"),
   512  					}
   513  				})
   514  
   515  				It("should return collapsed as unsupported filenames are ignored", func() {
   516  					// We want to compare functions, which is not ideal.
   517  					expected := reflect.ValueOf(CollapsedToProfile).Pointer()
   518  					f, err := converter(m)
   519  					Expect(err).To(BeNil())
   520  					Expect(f).ToNot(BeNil())
   521  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   522  				})
   523  			})
   524  		})
   525  
   526  		Context("perf script", func() {
   527  			When("detect by content", func() {
   528  				var m ProfileFile
   529  
   530  				BeforeEach(func() {
   531  					m = ProfileFile{
   532  						Data: []byte("java 12688 [002] 6544038.708352: cpu-clock:\n\n"),
   533  					}
   534  				})
   535  
   536  				It("should return perf_script", func() {
   537  					// We want to compare functions, which is not ideal.
   538  					expected := reflect.ValueOf(PerfScriptToProfile).Pointer()
   539  					f, err := converter(m)
   540  					Expect(err).To(BeNil())
   541  					Expect(f).ToNot(BeNil())
   542  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   543  				})
   544  			})
   545  			When("detect by .txt extension and content", func() {
   546  				var m ProfileFile
   547  
   548  				BeforeEach(func() {
   549  					m = ProfileFile{
   550  						Name: "foo.txt",
   551  						Data: []byte("java 12688 [002] 6544038.708352: cpu-clock:\n\n"),
   552  					}
   553  				})
   554  
   555  				It("should return perf_script", func() {
   556  					// We want to compare functions, which is not ideal.
   557  					expected := reflect.ValueOf(PerfScriptToProfile).Pointer()
   558  					f, err := converter(m)
   559  					Expect(err).To(BeNil())
   560  					Expect(f).ToNot(BeNil())
   561  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   562  				})
   563  			})
   564  			When("detect by .perf_script extension", func() {
   565  				var m ProfileFile
   566  
   567  				BeforeEach(func() {
   568  					m = ProfileFile{
   569  						Name: "foo.perf_script",
   570  						Data: []byte("foo;bar 239"),
   571  					}
   572  				})
   573  
   574  				It("should return perf_script", func() {
   575  					// We want to compare functions, which is not ideal.
   576  					expected := reflect.ValueOf(PerfScriptToProfile).Pointer()
   577  					f, err := converter(m)
   578  					Expect(err).To(BeNil())
   579  					Expect(f).ToNot(BeNil())
   580  					Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected))
   581  				})
   582  			})
   583  		})
   584  
   585  		Context("with an empty ProfileFile", func() {
   586  			var m ProfileFile
   587  			It("should return an error", func() {
   588  				_, err := converter(m)
   589  				Expect(err).ToNot(Succeed())
   590  			})
   591  		})
   592  	})
   593  })
   594  
   595  var _ = Describe("Convert", func() {
   596  	It("converts malformed pprof", func() {
   597  		m := ProfileFile{
   598  			Type: "pprof",
   599  			Data: readFile("./testdata/cpu-unknown.pb.gz"),
   600  		}
   601  
   602  		f, err := converter(m)
   603  		Expect(err).To(BeNil())
   604  		Expect(f).ToNot(BeNil())
   605  
   606  		b, err := f(m.Data, "appname", Limits{MaxNodes: 1024})
   607  		Expect(err).To(BeNil())
   608  		Expect(b).ToNot(BeNil())
   609  	})
   610  
   611  	It("handles pprof invalid fields gracefully", func() {
   612  		p := &profilev1.Profile{
   613  			SampleType: []*profilev1.ValueType{
   614  				{Type: 1, Unit: 2},
   615  			},
   616  			Sample: []*profilev1.Sample{
   617  				{LocationId: []uint64{1}, Value: []int64{100}},
   618  			},
   619  			Location: []*profilev1.Location{
   620  				{Id: 1, Address: 0x1000, Line: []*profilev1.Line{{FunctionId: 1, Line: 10}}},
   621  			},
   622  			Function: []*profilev1.Function{
   623  				{Id: 1, Name: 1},
   624  			},
   625  			StringTable: []string{"", "cpu", "count", "main"},
   626  			PeriodType:  nil, // This is the problematic case
   627  		}
   628  
   629  		data, err := proto.Marshal(p)
   630  		Expect(err).To(BeNil())
   631  
   632  		m := ProfileFile{
   633  			Type: "pprof",
   634  			Data: data,
   635  		}
   636  
   637  		f, err := converter(m)
   638  		Expect(err).To(BeNil())
   639  		Expect(f).ToNot(BeNil())
   640  
   641  		b, err := f(m.Data, "test-profile", Limits{MaxNodes: 1024})
   642  		Expect(err).ToNot(BeNil())
   643  		Expect(err.Error()).To(ContainSubstring("PeriodType is nil"))
   644  		Expect(b).To(BeNil())
   645  	})
   646  
   647  	Describe("JSON", func() {
   648  		It("prunes tree", func() {
   649  			m := ProfileFile{
   650  				Type: "json",
   651  				Data: readFile("./testdata/profile.json"),
   652  			}
   653  
   654  			f, err := converter(m)
   655  			Expect(err).To(BeNil())
   656  			Expect(f).ToNot(BeNil())
   657  
   658  			b, err := f(m.Data, "appname", Limits{MaxNodes: 1})
   659  			Expect(err).To(BeNil())
   660  			Expect(b).ToNot(BeNil())
   661  
   662  			// 1 + total
   663  			Expect(len(b[0].FlamebearerProfileV1.Flamebearer.Levels)).To(Equal(2))
   664  		})
   665  	})
   666  })
   667  
   668  func readFile(path string) []byte {
   669  	f, err := ioutil.ReadFile(path)
   670  	if err != nil {
   671  		panic(err)
   672  	}
   673  	return f
   674  }