github.com/cznic/ql@v1.2.1-0.20181122101857-b60735abf8a0/storage_test.go (about)

     1  // Copyright (c) 2014 ql 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 ql
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"flag"
    11  	"fmt"
    12  	"io"
    13  	"io/ioutil"
    14  	"log"
    15  	"os"
    16  	"regexp"
    17  	"runtime/debug"
    18  	"strings"
    19  	"testing"
    20  )
    21  
    22  var (
    23  	oN        = flag.Int("N", 0, "")
    24  	oM        = flag.Int("M", 0, "")
    25  	oFastFail = flag.Bool("fastFail", false, "")
    26  	oSlow     = flag.Bool("slow", false, "Do not wrap storage tests in a single outer transaction, write everything to disk file. Very slow.")
    27  )
    28  
    29  var testdata []string
    30  
    31  func init() {
    32  	tests, err := ioutil.ReadFile("testdata.ql")
    33  	if err != nil {
    34  		log.Panic(err)
    35  	}
    36  
    37  	a := bytes.Split(tests, []byte("\n-- "))
    38  	pre := []byte("-- ")
    39  	pres := []byte("S ")
    40  	for _, v := range a[1:] {
    41  		switch {
    42  		case bytes.HasPrefix(v, pres):
    43  			v = append(pre, v...)
    44  			v = append([]byte(sample), v...)
    45  		default:
    46  			v = append(pre, v...)
    47  		}
    48  		testdata = append(testdata, string(v))
    49  	}
    50  }
    51  
    52  func typeof(v interface{}) (r int) { //NTYPE
    53  	switch v.(type) {
    54  	case bool:
    55  		return qBool
    56  	case complex64:
    57  		return qComplex64
    58  	case complex128:
    59  		return qComplex128
    60  	case float32:
    61  		return qFloat32
    62  	case float64:
    63  		return qFloat64
    64  	case int8:
    65  		return qInt8
    66  	case int16:
    67  		return qInt16
    68  	case int32:
    69  		return qInt32
    70  	case int64:
    71  		return qInt64
    72  	case string:
    73  		return qString
    74  	case uint8:
    75  		return qUint8
    76  	case uint16:
    77  		return qUint16
    78  	case uint32:
    79  		return qUint32
    80  	case uint64:
    81  		return qUint64
    82  	}
    83  	return
    84  }
    85  
    86  func stypeof(nm string, val interface{}) string {
    87  	if t := typeof(val); t != 0 {
    88  		return fmt.Sprintf("%c%s", t, nm)
    89  	}
    90  
    91  	switch val.(type) {
    92  	case idealComplex:
    93  		return fmt.Sprintf("c%s", nm)
    94  	case idealFloat:
    95  		return fmt.Sprintf("f%s", nm)
    96  	case idealInt:
    97  		return fmt.Sprintf("l%s", nm)
    98  	case idealRune:
    99  		return fmt.Sprintf("k%s", nm)
   100  	case idealUint:
   101  		return fmt.Sprintf("x%s", nm)
   102  	default:
   103  		return fmt.Sprintf("?%s", nm)
   104  	}
   105  }
   106  
   107  func dumpCols(cols []*col) string {
   108  	a := []string{}
   109  	for _, col := range cols {
   110  		a = append(a, fmt.Sprintf("%d:%s %s", col.index, col.name, typeStr(col.typ)))
   111  	}
   112  	return strings.Join(a, ",")
   113  }
   114  
   115  func dumpFlds(flds []*fld) string {
   116  	a := []string{}
   117  	for _, fld := range flds {
   118  		a = append(a, fmt.Sprintf("%s AS %s", fld.expr, fld.name))
   119  	}
   120  	return strings.Join(a, ",")
   121  }
   122  
   123  func recSetDump(rs Recordset) (s string, err error) {
   124  	recset := rs.(recordset)
   125  	p := recset.plan
   126  	a0 := append([]string(nil), p.fieldNames()...)
   127  	for i, v := range a0 {
   128  		a0[i] = fmt.Sprintf("%q", v)
   129  	}
   130  	a := []string{strings.Join(a0, ", ")}
   131  	if err := p.do(recset.ctx, func(id interface{}, data []interface{}) (bool, error) {
   132  		if err = expand(data); err != nil {
   133  			return false, err
   134  		}
   135  
   136  		a = append(a, fmt.Sprintf("%v", data))
   137  		return true, nil
   138  	}); err != nil {
   139  		return "", err
   140  	}
   141  	return strings.Join(a, "\n"), nil
   142  }
   143  
   144  // http://en.wikipedia.org/wiki/Join_(SQL)#Sample_tables
   145  const sample = `
   146       BEGIN TRANSACTION;
   147  		CREATE TABLE department (
   148  			DepartmentID   int,
   149  			DepartmentName string,
   150  		);
   151  
   152  		INSERT INTO department VALUES
   153  			(31, "Sales"),
   154  			(33, "Engineering"),
   155  			(34, "Clerical"),
   156  			(35, "Marketing"),
   157  		;
   158  
   159  		CREATE TABLE employee (
   160  			LastName     string,
   161  			DepartmentID int,
   162  		);
   163  
   164  		INSERT INTO employee VALUES
   165  			("Rafferty", 31),
   166  			("Jones", 33),
   167  			("Heisenberg", 33),
   168  			("Robinson", 34),
   169  			("Smith", 34),
   170  			("Williams", NULL),
   171  		;
   172       COMMIT;
   173  `
   174  
   175  func explained(db *DB, s stmt, tctx *TCtx) (string, error) {
   176  	src := "explain " + s.String()
   177  	rs, _, err := db.Run(tctx, src, int64(30))
   178  	if err != nil {
   179  		return "", err
   180  	}
   181  
   182  	rows, err := rs[0].Rows(-1, 0)
   183  	if err != nil {
   184  		return "", err
   185  	}
   186  
   187  	if !strings.HasPrefix(rows[0][0].(string), "┌") {
   188  		return "", nil
   189  	}
   190  
   191  	var a []string
   192  	for _, v := range rows {
   193  		a = append(a, v[0].(string))
   194  	}
   195  	return strings.Join(a, "\n"), nil
   196  }
   197  
   198  // Test provides a testing facility for alternative storage implementations.
   199  // The s.setup should return a freshly created and empty storage. Removing the
   200  // store from the system is the responsibility of the caller. The test only
   201  // guarantees not to panic on recoverable errors and return an error instead.
   202  // Test errors are not returned but reported to t.
   203  func test(t *testing.T, s testDB) (panicked error) {
   204  	defer func() {
   205  		if e := recover(); e != nil {
   206  			switch x := e.(type) {
   207  			case error:
   208  				panicked = x
   209  			default:
   210  				panicked = fmt.Errorf("%v", e)
   211  			}
   212  		}
   213  		if panicked != nil {
   214  			t.Errorf("PANIC: %v\n%s", panicked, debug.Stack())
   215  		}
   216  	}()
   217  
   218  	db, err := s.setup()
   219  	if err != nil {
   220  		t.Error(err)
   221  		return
   222  	}
   223  
   224  	tctx := NewRWCtx()
   225  	if !*oSlow {
   226  		if _, _, err := db.Execute(tctx, txBegin); err != nil {
   227  			t.Error(err)
   228  			return nil
   229  		}
   230  	}
   231  
   232  	if err = s.mark(); err != nil {
   233  		t.Error(err)
   234  		return
   235  	}
   236  
   237  	defer func() {
   238  		x := tctx
   239  		if *oSlow {
   240  			x = nil
   241  		}
   242  		if err = s.teardown(x); err != nil {
   243  			t.Error(err)
   244  		}
   245  	}()
   246  
   247  	chk := func(test int, err error, expErr string, re *regexp.Regexp) (ok bool) {
   248  		s := err.Error()
   249  		if re == nil {
   250  			t.Error("FAIL: ", test, s)
   251  			return false
   252  		}
   253  
   254  		if !re.MatchString(s) {
   255  			t.Error("FAIL: ", test, "error doesn't match:", s, "expected", expErr)
   256  			return false
   257  		}
   258  
   259  		return true
   260  	}
   261  
   262  	var logf *os.File
   263  	hasLogf := false
   264  	noErrors := true
   265  	if _, ok := s.(*memTestDB); ok {
   266  		if logf, err = ioutil.TempFile("", "ql-test-log-"); err != nil {
   267  			t.Error(err)
   268  			return nil
   269  		}
   270  
   271  		hasLogf = true
   272  	} else {
   273  		if logf, err = os.Create(os.DevNull); err != nil {
   274  			t.Error(err)
   275  			return nil
   276  		}
   277  	}
   278  
   279  	defer func() {
   280  		if hasLogf && noErrors {
   281  			func() {
   282  				if _, err := logf.Seek(0, 0); err != nil {
   283  					t.Error(err)
   284  					return
   285  				}
   286  
   287  				dst, err := os.Create("testdata.log")
   288  				if err != nil {
   289  					t.Error(err)
   290  					return
   291  				}
   292  
   293  				if _, err := io.Copy(dst, logf); err != nil {
   294  					t.Error(err)
   295  					return
   296  				}
   297  
   298  				if err := dst.Close(); err != nil {
   299  					t.Error(err)
   300  				}
   301  			}()
   302  		}
   303  
   304  		nm := logf.Name()
   305  		if err := logf.Close(); err != nil {
   306  			t.Error(err)
   307  		}
   308  
   309  		if hasLogf {
   310  			if err := os.Remove(nm); err != nil {
   311  				t.Error(err)
   312  			}
   313  		}
   314  	}()
   315  
   316  	log := bufio.NewWriter(logf)
   317  
   318  	defer func() {
   319  		if err := log.Flush(); err != nil {
   320  			t.Error(err)
   321  		}
   322  	}()
   323  
   324  	max := len(testdata)
   325  	if n := *oM; n != 0 && n < max {
   326  		max = n
   327  	}
   328  	for itest, test := range testdata[*oN:max] {
   329  		var re *regexp.Regexp
   330  		a := strings.Split(test+"|", "|")
   331  		q, rset := a[0], strings.TrimSpace(a[1])
   332  		var expErr string
   333  		if len(a) < 3 {
   334  			t.Error(itest, "internal error 066")
   335  			return
   336  		}
   337  
   338  		if expErr = a[2]; expErr != "" {
   339  			re = regexp.MustCompile("(?i:" + strings.TrimSpace(expErr) + ")")
   340  		}
   341  
   342  		q = strings.Replace(q, "&or;", "|", -1)
   343  		q = strings.Replace(q, "&oror;", "||", -1)
   344  		list, err := Compile(q)
   345  		if err != nil {
   346  			if !chk(itest, err, expErr, re) && *oFastFail {
   347  				return
   348  			}
   349  
   350  			continue
   351  		}
   352  
   353  		for _, s := range list.l {
   354  			if err := testMentionedColumns(s); err != nil {
   355  				t.Error(itest, err)
   356  				return
   357  			}
   358  		}
   359  
   360  		s1 := list.String()
   361  		list1, err := Compile(s1)
   362  		if err != nil {
   363  			t.Errorf("recreated source does not compile: %v\n---- orig\n%s\n---- recreated\n%s", err, q, s1)
   364  			if *oFastFail {
   365  				return
   366  			}
   367  
   368  			continue
   369  		}
   370  
   371  		s2 := list1.String()
   372  		if g, e := s2, s1; g != e {
   373  			t.Errorf("recreated source is not idempotent\n---- orig\n%s\n---- recreated1\n%s\n---- recreated2\n%s", q, s1, s2)
   374  			if *oFastFail {
   375  				return
   376  			}
   377  
   378  			continue
   379  		}
   380  
   381  		if !func() (ok bool) {
   382  			tnl0 := db.tnl
   383  			defer func() {
   384  				s3 := list.String()
   385  				if g, e := s1, s3; g != e {
   386  					t.Errorf("#%d: execution mutates compiled statement list\n---- orig\n%s----new\n%s", itest, g, e)
   387  				}
   388  
   389  				if !ok {
   390  					noErrors = false
   391  				}
   392  
   393  				if noErrors {
   394  					hdr := false
   395  					for _, v := range list.l {
   396  						s, err := explained(db, v, tctx)
   397  						if err != nil {
   398  							t.Error(err)
   399  							return
   400  						}
   401  
   402  						if !strings.HasPrefix(s, "┌") {
   403  							continue
   404  						}
   405  
   406  						if !hdr {
   407  							fmt.Fprintf(log, "---- %v\n", itest)
   408  							hdr = true
   409  						}
   410  						fmt.Fprintf(log, "%s\n", v)
   411  						fmt.Fprintf(log, "%s\n\n", s)
   412  					}
   413  				}
   414  
   415  				tnl := db.tnl
   416  				if tnl != tnl0 {
   417  					panic(fmt.Errorf("internal error 057: tnl0 %v, tnl %v", tnl0, tnl))
   418  				}
   419  
   420  				nfo, err := db.Info()
   421  				if err != nil {
   422  					//dbg("", err)
   423  					panic(err)
   424  				}
   425  
   426  				for _, idx := range nfo.Indices {
   427  					//dbg("#%d: cleanup index %s", itest, idx.Name)
   428  					if _, _, err = db.run(tctx, fmt.Sprintf(`
   429  						BEGIN TRANSACTION;
   430  							DROP INDEX %s;
   431  						COMMIT;
   432  						`,
   433  						idx.Name)); err != nil {
   434  						t.Errorf("#%d: cleanup DROP INDEX %s: %v", itest, idx.Name, err)
   435  						ok = false
   436  					}
   437  				}
   438  				for _, tab := range nfo.Tables {
   439  					//dbg("#%d: cleanup table %s", itest, tab.Name)
   440  					if _, _, err = db.run(tctx, fmt.Sprintf(`
   441  						BEGIN TRANSACTION;
   442  							DROP table %s;
   443  						COMMIT;
   444  						`,
   445  						tab.Name)); err != nil {
   446  						t.Errorf("#%d: cleanup DROP TABLE %s: %v", itest, tab.Name, err)
   447  						ok = false
   448  					}
   449  				}
   450  				db.hasIndex2 = 0
   451  			}()
   452  
   453  			if err = s.mark(); err != nil {
   454  				t.Error(itest, err)
   455  				return
   456  			}
   457  
   458  			rs, _, err := db.Execute(tctx, list, int64(30))
   459  			if err != nil {
   460  				return chk(itest, err, expErr, re)
   461  			}
   462  
   463  			if rs == nil {
   464  				t.Errorf("FAIL: %d: expected non nil Recordset or error %q", itest, expErr)
   465  				return
   466  			}
   467  
   468  			g, err := recSetDump(rs[len(rs)-1])
   469  			if err != nil {
   470  				return chk(itest, err, expErr, re)
   471  			}
   472  
   473  			if expErr != "" {
   474  				t.Errorf("FAIL: %d: expected error %q", itest, expErr)
   475  				return
   476  			}
   477  
   478  			a = strings.Split(rset, "\n")
   479  			for i, v := range a {
   480  				a[i] = strings.TrimSpace(v)
   481  			}
   482  			e := strings.Join(a, "\n")
   483  			if g != e {
   484  				t.Errorf("FAIL: test # %d\n%s\n---- g\n%s\n---- e\n%s\n----", itest, q, g, e)
   485  				return
   486  			}
   487  
   488  			return true
   489  		}() && *oFastFail {
   490  			return
   491  		}
   492  	}
   493  	return
   494  }