github.com/astrogo/fitsio@v0.3.0/header_test.go (about)

     1  // Copyright 2015 The astrogo 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 fitsio
     6  
     7  import (
     8  	"fmt"
     9  	"io/ioutil"
    10  	"math"
    11  	"math/big"
    12  	"os"
    13  	"reflect"
    14  	"testing"
    15  )
    16  
    17  func newBigInt(t *testing.T) big.Int {
    18  	var i big.Int
    19  	_, err := fmt.Sscanf("40002100000000422948", "%v", &i)
    20  	if err != nil {
    21  		t.Fatalf("error creating a big.Int: %v\n", err)
    22  	}
    23  	return i
    24  }
    25  
    26  func TestHeaderRW(t *testing.T) {
    27  	curdir, err := os.Getwd()
    28  	if err != nil {
    29  		t.Fatalf(err.Error())
    30  	}
    31  	defer os.Chdir(curdir)
    32  
    33  	workdir, err := ioutil.TempDir("", "go-fitsio-test-")
    34  	if err != nil {
    35  		t.Fatalf(err.Error())
    36  	}
    37  	defer os.RemoveAll(workdir)
    38  
    39  	err = os.Chdir(workdir)
    40  	if err != nil {
    41  		t.Fatalf(err.Error())
    42  	}
    43  
    44  	table := struct {
    45  		name    string
    46  		version int
    47  		cards   []Card
    48  		bitpix  int
    49  		axes    []int
    50  		image   interface{}
    51  	}{
    52  		name:    "new.fits",
    53  		version: 2,
    54  		cards: []Card{
    55  			{
    56  				"EXTNAME",
    57  				"primary hdu",
    58  				"the primary HDU",
    59  			},
    60  			{
    61  				"EXTVER",
    62  				2,
    63  				"the primary hdu version",
    64  			},
    65  			{
    66  				"card_uint8",
    67  				byte(42),
    68  				"an uint8",
    69  			},
    70  			{
    71  				"card_uint16",
    72  				uint16(42),
    73  				"an uint16",
    74  			},
    75  			{
    76  				"card_uint32",
    77  				uint32(42),
    78  				"an uint32",
    79  			},
    80  			{
    81  				"card_uint64",
    82  				uint64(42),
    83  				"an uint64",
    84  			},
    85  			{
    86  				"card_int8",
    87  				int8(42),
    88  				"an int8",
    89  			},
    90  			{
    91  				"card_int16",
    92  				int16(42),
    93  				"an int16",
    94  			},
    95  			{
    96  				"card_int32",
    97  				int32(42),
    98  				"an int32",
    99  			},
   100  			{
   101  				"card_int64",
   102  				int64(42),
   103  				"an int64",
   104  			},
   105  			{
   106  				"card_int3264",
   107  				int(42),
   108  				"an int",
   109  			},
   110  			{
   111  				"card_uintxx",
   112  				uint(42),
   113  				"an uint",
   114  			},
   115  			{
   116  				"card_float32",
   117  				float32(666),
   118  				"a float32",
   119  			},
   120  			{
   121  				"card_float64",
   122  				float64(666),
   123  				"a float64",
   124  			},
   125  			{
   126  				"card_complex64",
   127  				complex(float32(42), float32(66)),
   128  				"a complex64",
   129  			},
   130  			{
   131  				"card_complex128",
   132  				complex(float64(42), float64(66)),
   133  				"a complex128",
   134  			},
   135  			{
   136  				"card_bigint",
   137  				newBigInt(t),
   138  				"a big int",
   139  			},
   140  		},
   141  		bitpix: 8,
   142  		axes:   []int{},
   143  	}
   144  	fname := "new.fits"
   145  	for _, fct := range []func(){
   146  		// create
   147  		func() {
   148  			w, err := os.Create(fname)
   149  			if err != nil {
   150  				t.Fatalf("error creating new file [%v]: %v", fname, err)
   151  			}
   152  			defer w.Close()
   153  
   154  			f, err := Create(w)
   155  			if err != nil {
   156  				t.Fatalf("error creating new file [%v]: %v", fname, err)
   157  			}
   158  			defer f.Close()
   159  
   160  			phdr := NewHeader(
   161  				table.cards,
   162  				IMAGE_HDU,
   163  				table.bitpix,
   164  				table.axes,
   165  			)
   166  			phdu, err := NewPrimaryHDU(phdr)
   167  			if err != nil {
   168  				t.Fatalf("error creating PHDU: %v", err)
   169  			}
   170  			defer phdu.Close()
   171  
   172  			hdr := phdu.Header()
   173  			if hdr.bitpix != table.bitpix {
   174  				t.Fatalf("expected BITPIX=%v. got %v", table.bitpix, hdr.bitpix)
   175  			}
   176  
   177  			name := phdu.Name()
   178  			if name != "primary hdu" {
   179  				t.Fatalf("expected EXTNAME==%q. got %q", "primary hdu", name)
   180  			}
   181  
   182  			vers := phdu.Version()
   183  			if vers != table.version {
   184  				t.Fatalf("expected EXTVER==%v. got %v", table.version, vers)
   185  			}
   186  
   187  			card := hdr.Get("EXTNAME")
   188  			if card == nil {
   189  				t.Fatalf("error retrieving card [EXTNAME]")
   190  			}
   191  			if card.Comment != "the primary HDU" {
   192  				t.Fatalf("expected EXTNAME.Comment==%q. got %q", "the primary HDU", card.Comment)
   193  			}
   194  
   195  			card = hdr.Get("EXTVER")
   196  			if card == nil {
   197  				t.Fatalf("error retrieving card [EXTVER]")
   198  			}
   199  			if card.Comment != "the primary hdu version" {
   200  				t.Fatalf("expected EXTVER.Comment==%q. got %q", "the primary hdu version", card.Comment)
   201  
   202  			}
   203  
   204  			for _, ref := range table.cards {
   205  				card := hdr.Get(ref.Name)
   206  				if card == nil {
   207  					t.Fatalf("error retrieving card [%v]", ref.Name)
   208  				}
   209  				rv := reflect.ValueOf(ref.Value)
   210  				var val interface{}
   211  				switch rv.Type().Kind() {
   212  				case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   213  					val = int(rv.Int())
   214  				case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   215  					val = int(rv.Uint())
   216  				case reflect.Float32, reflect.Float64:
   217  					val = rv.Float()
   218  				case reflect.Complex64, reflect.Complex128:
   219  					val = rv.Complex()
   220  				case reflect.String:
   221  					val = ref.Value.(string)
   222  				case reflect.Bool:
   223  					val = ref.Value.(bool)
   224  				case reflect.Struct:
   225  					switch ref.Value.(type) {
   226  					case big.Int:
   227  						val = ref.Value.(big.Int)
   228  					}
   229  				}
   230  				if !reflect.DeepEqual(card.Value, val) {
   231  					t.Fatalf(
   232  						"card %q. expected [%v](%T). got [%v](%T)",
   233  						ref.Name,
   234  						val, val,
   235  						card.Value, card.Value,
   236  					)
   237  				}
   238  				if card.Comment != ref.Comment {
   239  					t.Fatalf("card %q. comment differ. expected %q. got %q", ref.Name, ref.Comment, card.Comment)
   240  				}
   241  			}
   242  
   243  			card = hdr.Get("NOT THERE")
   244  			if card != nil {
   245  				t.Fatalf("expected no card. got [%v]", card)
   246  			}
   247  
   248  			err = f.Write(phdu)
   249  			if err != nil {
   250  				t.Fatalf("error writing hdu to file: %v", err)
   251  			}
   252  		},
   253  		// read-back
   254  		func() {
   255  			r, err := os.Open(fname)
   256  			if err != nil {
   257  				t.Fatalf("error opening file [%v]: %v", fname, err)
   258  			}
   259  			defer r.Close()
   260  			f, err := Open(r)
   261  			if err != nil {
   262  				buf, _ := ioutil.ReadFile(fname)
   263  				t.Fatalf("error opening file [%v]: %v\nbuf=%s\n", fname, err, string(buf))
   264  			}
   265  			defer f.Close()
   266  
   267  			hdu := f.HDU(0)
   268  			hdr := hdu.Header()
   269  			if hdr.bitpix != table.bitpix {
   270  				t.Fatalf("expected BITPIX=%v. got %v", 8, hdr.bitpix)
   271  			}
   272  
   273  			name := hdu.Name()
   274  			if name != "primary hdu" {
   275  				t.Fatalf("expected EXTNAME==%q. got %q", "primary hdu", name)
   276  			}
   277  
   278  			vers := hdu.Version()
   279  			if vers != table.version {
   280  				t.Fatalf("expected EXTVER==%v. got %v", 2, vers)
   281  			}
   282  
   283  			card := hdr.Get("EXTNAME")
   284  			if card == nil {
   285  				t.Fatalf("error retrieving card [EXTNAME]")
   286  			}
   287  			if card.Comment != "the primary HDU" {
   288  				t.Fatalf("expected EXTNAME.Comment==%q. got %q", "the primary HDU", card.Comment)
   289  			}
   290  
   291  			card = hdr.Get("EXTVER")
   292  			if card == nil {
   293  				t.Fatalf("error retrieving card [EXTVER]")
   294  			}
   295  			if card.Comment != "the primary hdu version" {
   296  				t.Fatalf("expected EXTVER.Comment==%q. got %q", "the primary hdu version", card.Comment)
   297  
   298  			}
   299  
   300  			for _, ref := range table.cards {
   301  				card := hdr.Get(ref.Name)
   302  				if card == nil {
   303  					t.Fatalf("error retrieving card [%v]", ref.Name)
   304  				}
   305  
   306  				rv := reflect.ValueOf(ref.Value)
   307  				var val interface{}
   308  				switch rv.Type().Kind() {
   309  				case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   310  					val = int(rv.Int())
   311  				case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   312  					val = int(rv.Uint())
   313  				case reflect.Float32, reflect.Float64:
   314  					val = rv.Float()
   315  				case reflect.Complex64, reflect.Complex128:
   316  					val = rv.Complex()
   317  				case reflect.String:
   318  					val = ref.Value.(string)
   319  				case reflect.Bool:
   320  					val = ref.Value.(bool)
   321  				case reflect.Struct:
   322  					switch ref.Value.(type) {
   323  					case big.Int:
   324  						val = ref.Value.(big.Int)
   325  					}
   326  				}
   327  				if !reflect.DeepEqual(card.Value, val) {
   328  					t.Fatalf(
   329  						"card %q. expected [%v](%T). got [%v](%T)",
   330  						ref.Name,
   331  						val, val,
   332  						card.Value, card.Value,
   333  					)
   334  				}
   335  
   336  				if card.Comment != ref.Comment {
   337  					t.Fatalf("card %q. comment differ. expected %q. got %q", ref.Name, ref.Comment, card.Comment)
   338  				}
   339  			}
   340  
   341  			card = hdr.Get("NOT THERE")
   342  			if card != nil {
   343  				t.Fatalf("expected no card. got [%v]", card)
   344  			}
   345  		},
   346  	} {
   347  		fct()
   348  	}
   349  }
   350  
   351  func TestRWHeaderLine(t *testing.T) {
   352  	for _, table := range []struct {
   353  		line []byte
   354  		card *Card
   355  		err  error
   356  	}{
   357  		{
   358  			line: []byte("SIMPLE  =                    T / file does conform to FITS standard             "),
   359  			card: &Card{
   360  				Name:    "SIMPLE",
   361  				Value:   true,
   362  				Comment: "file does conform to FITS standard",
   363  			},
   364  			err: nil,
   365  		},
   366  		{
   367  			line: []byte("BITPIX  =                   16 / number of bits per data pixel                  "),
   368  			card: &Card{
   369  				Name:    "BITPIX",
   370  				Value:   16,
   371  				Comment: "number of bits per data pixel",
   372  			},
   373  			err: nil,
   374  		},
   375  		{
   376  			line: []byte("EXTNAME = 'primary hdu'        / the primary HDU                                "),
   377  			card: &Card{
   378  				Name:    "EXTNAME",
   379  				Value:   "primary hdu",
   380  				Comment: "the primary HDU",
   381  			},
   382  			err: nil,
   383  		},
   384  		{
   385  			line: []byte("STRING  = 'a / '              / comment                                         "),
   386  			card: &Card{
   387  				Name:    "STRING",
   388  				Value:   "a /",
   389  				Comment: "comment",
   390  			},
   391  			err: nil,
   392  		},
   393  		{
   394  			line: []byte("STRING  = ' a / '             / comment                                         "),
   395  			card: &Card{
   396  				Name:    "STRING",
   397  				Value:   " a /",
   398  				Comment: "comment",
   399  			},
   400  			err: nil,
   401  		},
   402  		{
   403  			line: []byte("STRING  = ' a /              / comment                                        |'"),
   404  			card: &Card{
   405  				Name:    "STRING",
   406  				Value:   " a /              / comment                                        |",
   407  				Comment: "",
   408  			},
   409  			err: nil,
   410  		},
   411  		{
   412  			line: []byte("STRING  = ' a /              / comment                                         '"),
   413  			card: &Card{
   414  				Name:    "STRING",
   415  				Value:   " a /              / comment",
   416  				Comment: "",
   417  			},
   418  			err: nil,
   419  		},
   420  		{
   421  			line: []byte("STRING  = 'a / '''            / comment                                         "),
   422  			card: &Card{
   423  				Name:    "STRING",
   424  				Value:   "a / '",
   425  				Comment: "comment",
   426  			},
   427  			err: nil,
   428  		},
   429  		{
   430  			line: []byte("COMPLEX =        (42.0, 66.0) / comment                                         "),
   431  			card: &Card{
   432  				Name:    "COMPLEX",
   433  				Value:   complex(42, 66),
   434  				Comment: "comment",
   435  			},
   436  			err: nil,
   437  		},
   438  		{
   439  			line: []byte("COMPLEX =         (42.0,66.0) / comment                                         "),
   440  			card: &Card{
   441  				Name:    "COMPLEX",
   442  				Value:   complex(42, 66),
   443  				Comment: "comment",
   444  			},
   445  			err: nil,
   446  		},
   447  		{
   448  			line: []byte("COMPLEX =             (42,66) / comment                                         "),
   449  			card: &Card{
   450  				Name:    "COMPLEX",
   451  				Value:   complex(42, 66),
   452  				Comment: "comment",
   453  			},
   454  			err: nil,
   455  		},
   456  		{
   457  			line: []byte("COMPLEX =           (42.0,66) / comment                                         "),
   458  			card: &Card{
   459  				Name:    "COMPLEX",
   460  				Value:   complex(42, 66),
   461  				Comment: "comment",
   462  			},
   463  			err: nil,
   464  		},
   465  		{
   466  			line: []byte("COMPLEX =           (42,66.0) / comment                                         "),
   467  			card: &Card{
   468  				Name:    "COMPLEX",
   469  				Value:   complex(42, 66),
   470  				Comment: "comment",
   471  			},
   472  			err: nil,
   473  		},
   474  		{
   475  			line: []byte("COMPLEX = (42.000,66.0000)    / comment                                         "),
   476  			card: &Card{
   477  				Name:    "COMPLEX",
   478  				Value:   complex(42, 66),
   479  				Comment: "comment",
   480  			},
   481  			err: nil,
   482  		},
   483  	} {
   484  		card, err := parseHeaderLine(table.line)
   485  		if !reflect.DeepEqual(err, table.err) {
   486  			t.Fatalf("expected error [%v]. got: %v", table.err, err)
   487  		}
   488  		if !reflect.DeepEqual(card, table.card) {
   489  			t.Fatalf("cards differ.\nexp= %#v\ngot= %#v", table.card, card)
   490  		}
   491  
   492  		line, err := makeHeaderLine(card)
   493  		if err != nil {
   494  			t.Fatalf("error making header-line: %v (%s)", err, string(line))
   495  		}
   496  	}
   497  
   498  	for _, table := range []struct {
   499  		line []byte
   500  		err  error
   501  	}{
   502  		{
   503  			line: []byte("SIMPLE= T / FITS file"),
   504  			err:  fmt.Errorf("fitsio: invalid header line length"),
   505  		},
   506  		{
   507  			line: []byte("STRING  = 'foo                   / comment                                      "),
   508  			err:  fmt.Errorf(`fitsio: string ends prematurely ("'foo                   / comment                                      ")`),
   509  		},
   510  		{
   511  			line: []byte("STRING  = 'foo ''                / comment                                      "),
   512  			err:  fmt.Errorf(`fitsio: string ends prematurely ("'foo ''                / comment                                      ")`),
   513  		},
   514  	} {
   515  		card, err := parseHeaderLine(table.line)
   516  		if !reflect.DeepEqual(err, table.err) {
   517  			t.Fatalf("expected error [%v]. got: %v\ncard=%#v", table.err, err, card)
   518  		}
   519  		if card != nil {
   520  			t.Fatalf("expected a nil card. got= %#v", card)
   521  		}
   522  	}
   523  }
   524  
   525  func TestMakeHeaderLine(t *testing.T) {
   526  	for _, table := range []struct {
   527  		card *Card
   528  		line []byte
   529  		err  error
   530  	}{
   531  		{
   532  			card: &Card{
   533  				Name:    "SIMPLE",
   534  				Value:   true,
   535  				Comment: "file does conform to FITS standard",
   536  			},
   537  			line: []byte("SIMPLE  =                    T / file does conform to FITS standard             "),
   538  			err:  nil,
   539  		},
   540  		{
   541  			line: []byte("STRING  = ' a /              / no-comment                                    1&'CONTINUE  '2|      '                                                            COMMENT my-comment                                                              "),
   542  			card: &Card{
   543  				Name:    "STRING",
   544  				Value:   " a /              / no-comment                                    12|",
   545  				Comment: "my-comment",
   546  			},
   547  			err: nil,
   548  		},
   549  		{
   550  			line: []byte("STRING  = ' a /              / no-comment                                    1&'CONTINUE  '2|      '                                                            "),
   551  			card: &Card{
   552  				Name:    "STRING",
   553  				Value:   " a /              / no-comment                                    12|",
   554  				Comment: "",
   555  			},
   556  			err: nil,
   557  		},
   558  		{
   559  			line: []byte("STRING  = ' a /              / no-comment                                    |&'CONTINUE  '0123456789012345678901234567890123456789012345678901234567890123456&'CONTINUE  '7890123456789|'                                                      "),
   560  			card: &Card{
   561  				Name:    "STRING",
   562  				Value:   " a /              / no-comment                                    |01234567890123456789012345678901234567890123456789012345678901234567890123456789|",
   563  				Comment: "",
   564  			},
   565  			err: nil,
   566  		},
   567  		{
   568  			line: []byte("STRING  = ' a /              / no-comment                                    |&'CONTINUE  '0123456789012345678901234567890123456789012345678901234567890123456&'CONTINUE  '7890123456789|'                                                      COMMENT my-comment                                                              "),
   569  			card: &Card{
   570  				Name:    "STRING",
   571  				Value:   " a /              / no-comment                                    |01234567890123456789012345678901234567890123456789012345678901234567890123456789|",
   572  				Comment: "my-comment",
   573  			},
   574  			err: nil,
   575  		},
   576  		{
   577  			line: []byte("COMMENT *                                                                       "),
   578  			card: &Card{
   579  				Name:    "COMMENT",
   580  				Value:   "",
   581  				Comment: "*",
   582  			},
   583  			err: nil,
   584  		},
   585  		{
   586  			line: []byte("FLOAT64 =   1.1234567891234568 / small float value                              "),
   587  			card: &Card{
   588  				Name:    "FLOAT64",
   589  				Value:   float64(1.123456789123456789123456789),
   590  				Comment: "small float value",
   591  			},
   592  			err: nil,
   593  		},
   594  		{
   595  			line: []byte("FLOAT64 = 9.223372036854776E+18 / large float value                             "),
   596  			card: &Card{
   597  				Name:    "FLOAT64",
   598  				Value:   float64(9223372036854775807.123456789123456789123456789),
   599  				Comment: "large float value",
   600  			},
   601  			err: nil,
   602  		},
   603  		{
   604  			line: []byte("HIERARCH DIVFLOAT64=   0.6666666666666666 / infinit                             "),
   605  			card: &Card{
   606  				Name:    "DIVFLOAT64",
   607  				Value:   float64(2.0 / 3.0),
   608  				Comment: "infinit",
   609  			},
   610  			err: nil,
   611  		},
   612  		{
   613  			line: []byte("HIERARCH MAXFLOAT64= 1.7976931348623157E+308 / MaxFloat64                       "),
   614  			card: &Card{
   615  				Name:    "MAXFLOAT64",
   616  				Value:   math.MaxFloat64,
   617  				Comment: "MaxFloat64",
   618  			},
   619  			err: nil,
   620  		},
   621  		{
   622  			line: []byte("HIERARCH SMALLESTNONZEROFLOAT=         5.00000E-324 / SmallestNonzeroFloat64    "),
   623  			card: &Card{
   624  				Name:    "SMALLESTNONZEROFLOAT",
   625  				Value:   math.SmallestNonzeroFloat64,
   626  				Comment: "SmallestNonzeroFloat64",
   627  			},
   628  			err: nil,
   629  		},
   630  	} {
   631  		line, err := makeHeaderLine(table.card)
   632  		if !reflect.DeepEqual(err, table.err) {
   633  			t.Fatalf("expected error [%v]. got: %v\nline=%q", table.err, err, string(line))
   634  		}
   635  		if !reflect.DeepEqual(line, table.line) {
   636  			t.Fatalf("bline differ.\nexp=%q\ngot=%q", string(table.line), string(line))
   637  		}
   638  	}
   639  }
   640  
   641  func TestDuplicateCardKeys(t *testing.T) {
   642  	r, err := os.Open("testdata/issue-38.fits")
   643  	if err != nil {
   644  		t.Fatal(err)
   645  	}
   646  	defer r.Close()
   647  
   648  	f, err := Open(r)
   649  	if err != nil {
   650  		t.Fatal(err)
   651  	}
   652  	defer f.Close()
   653  
   654  	hdu := f.HDU(0)
   655  	hdr := hdu.Header()
   656  	c := hdr.Get("DUP")
   657  	want := 1
   658  	if !reflect.DeepEqual(c.Value, want) {
   659  		t.Fatalf("got %v for duplicate key. want %v (the first one)", c.Value, want)
   660  	}
   661  }