github.com/runner-mei/ql@v1.1.0/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  		//dbg("------------------------------------------------------------- ( itest %d ) ----", itest)
   330  		var re *regexp.Regexp
   331  		a := strings.Split(test+"|", "|")
   332  		q, rset := a[0], strings.TrimSpace(a[1])
   333  		var expErr string
   334  		if len(a) < 3 {
   335  			t.Error(itest, "internal error 066")
   336  			return
   337  		}
   338  
   339  		if expErr = a[2]; expErr != "" {
   340  			re = regexp.MustCompile("(?i:" + strings.TrimSpace(expErr) + ")")
   341  		}
   342  
   343  		q = strings.Replace(q, "&or;", "|", -1)
   344  		q = strings.Replace(q, "&oror;", "||", -1)
   345  		list, err := Compile(q)
   346  		if err != nil {
   347  			if !chk(itest, err, expErr, re) && *oFastFail {
   348  				return
   349  			}
   350  
   351  			continue
   352  		}
   353  
   354  		for _, s := range list.l {
   355  			if err := testMentionedColumns(s); err != nil {
   356  				t.Error(itest, err)
   357  				return
   358  			}
   359  		}
   360  
   361  		s1 := list.String()
   362  		list1, err := Compile(s1)
   363  		if err != nil {
   364  			t.Errorf("recreated source does not compile: %v\n---- orig\n%s\n---- recreated\n%s", err, q, s1)
   365  			if *oFastFail {
   366  				return
   367  			}
   368  
   369  			continue
   370  		}
   371  
   372  		s2 := list1.String()
   373  		if g, e := s2, s1; g != e {
   374  			t.Errorf("recreated source is not idempotent\n---- orig\n%s\n---- recreated1\n%s\n---- recreated2\n%s", q, s1, s2)
   375  			if *oFastFail {
   376  				return
   377  			}
   378  
   379  			continue
   380  		}
   381  
   382  		if !func() (ok bool) {
   383  			tnl0 := db.tnl
   384  			defer func() {
   385  				s3 := list.String()
   386  				if g, e := s1, s3; g != e {
   387  					t.Errorf("#%d: execution mutates compiled statement list\n---- orig\n%s----new\n%s", itest, g, e)
   388  				}
   389  
   390  				if !ok {
   391  					noErrors = false
   392  				}
   393  
   394  				if noErrors {
   395  					hdr := false
   396  					for _, v := range list.l {
   397  						s, err := explained(db, v, tctx)
   398  						if err != nil {
   399  							t.Error(err)
   400  							return
   401  						}
   402  
   403  						if !strings.HasPrefix(s, "┌") {
   404  							continue
   405  						}
   406  
   407  						if !hdr {
   408  							fmt.Fprintf(log, "---- %v\n", itest)
   409  							hdr = true
   410  						}
   411  						fmt.Fprintf(log, "%s\n", v)
   412  						fmt.Fprintf(log, "%s\n\n", s)
   413  					}
   414  				}
   415  
   416  				tnl := db.tnl
   417  				if tnl != tnl0 {
   418  					panic(fmt.Errorf("internal error 057: tnl0 %v, tnl %v", tnl0, tnl))
   419  				}
   420  
   421  				nfo, err := db.Info()
   422  				if err != nil {
   423  					dbg("", err)
   424  					panic(err)
   425  				}
   426  
   427  				for _, idx := range nfo.Indices {
   428  					//dbg("#%d: cleanup index %s", itest, idx.Name)
   429  					if _, _, err = db.run(tctx, fmt.Sprintf(`
   430  						BEGIN TRANSACTION;
   431  							DROP INDEX %s;
   432  						COMMIT;
   433  						`,
   434  						idx.Name)); err != nil {
   435  						t.Errorf("#%d: cleanup DROP INDEX %s: %v", itest, idx.Name, err)
   436  						ok = false
   437  					}
   438  				}
   439  				for _, tab := range nfo.Tables {
   440  					//dbg("#%d: cleanup table %s", itest, tab.Name)
   441  					if _, _, err = db.run(tctx, fmt.Sprintf(`
   442  						BEGIN TRANSACTION;
   443  							DROP table %s;
   444  						COMMIT;
   445  						`,
   446  						tab.Name)); err != nil {
   447  						t.Errorf("#%d: cleanup DROP TABLE %s: %v", itest, tab.Name, err)
   448  						ok = false
   449  					}
   450  				}
   451  				db.hasIndex2 = 0
   452  			}()
   453  
   454  			if err = s.mark(); err != nil {
   455  				t.Error(err)
   456  				return
   457  			}
   458  
   459  			rs, _, err := db.Execute(tctx, list, int64(30))
   460  			if err != nil {
   461  				return chk(itest, err, expErr, re)
   462  			}
   463  
   464  			if rs == nil {
   465  				t.Errorf("FAIL: %d: expected non nil Recordset or error %q", itest, expErr)
   466  				return
   467  			}
   468  
   469  			g, err := recSetDump(rs[len(rs)-1])
   470  			if err != nil {
   471  				return chk(itest, err, expErr, re)
   472  			}
   473  
   474  			if expErr != "" {
   475  				t.Errorf("FAIL: %d: expected error %q", itest, expErr)
   476  				return
   477  			}
   478  
   479  			a = strings.Split(rset, "\n")
   480  			for i, v := range a {
   481  				a[i] = strings.TrimSpace(v)
   482  			}
   483  			e := strings.Join(a, "\n")
   484  			if g != e {
   485  				t.Errorf("FAIL: test # %d\n%s\n---- g\n%s\n---- e\n%s\n----", itest, q, g, e)
   486  				return
   487  			}
   488  
   489  			return true
   490  		}() && *oFastFail {
   491  			return
   492  		}
   493  	}
   494  	return
   495  }