Example #1
0
func Test_PythonPlugin_builtins(t *testing.T) {
	cleanup := testutils.Chtemp()
	defer cleanup()

	// this isn't really the builtin Fubsy println() function: we can't
	// use it because it's in the runtime package, and we don't want
	// to because it has side-effects... but we're stuck with a
	// hardcoded set of builtin function names for now, so we have to
	// reuse one of them
	calls := []string{}
	fn_println := func(args types.ArgSource) (types.FuObject, []error) {
		s := args.Args()[0].ValueString()
		calls = append(calls, s)
		return nil, nil
	}
	builtins := StubBuiltinList{types.NewFixedFunction("println", 1, fn_println)}

	pp, err := LoadMetaPlugin("python2", builtins)
	testutils.NoError(t, err)

	values, err := pp.Run(`
fubsy.println("ding")
fubsy.println("dong")
`)
	_ = values
	expect := []string{"ding", "dong"}
	testutils.NoError(t, err)
	assert.Equal(t, expect, calls)
}
Example #2
0
func Test_KyotoDB_key_prefix(t *testing.T) {
	// make sure we write the key exactly as expected, byte-for-byte
	cleanup := testutils.Chtemp()
	defer cleanup()

	db, err := OpenKyotoDB("test1.kch", true)
	if err != nil {
		t.Fatal(err)
	}
	assert.NotNil(t, db.kcdb)

	rec1 := NewBuildRecord()
	rec1.SetTargetSignature([]byte{})
	db.WriteNode("f", rec1)
	db.WriteNode("foobar", rec1)
	db.WriteNode("foo", rec1)

	// hmmm: does Kyoto Cabinet guarantee key order? it seems to
	// preserve insertion order from what I can tell, but I'm not
	// sure if that's reliable
	expect := []string{
		"\x00\x00\x00\x00version",
		"\x00\x00\x00\x01f",
		"\x00\x00\x00\x01foobar",
		"\x00\x00\x00\x01foo",
	}
	keychan := db.kcdb.Keys()
	for i, expectstr := range expect {
		expectkey := ([]byte)(expectstr)
		actualkey := <-keychan
		if !bytes.Equal(expectkey, actualkey) {
			t.Errorf("key %d: expected\n%v\nbut got\n%v", i, expectkey, actualkey)
		}
	}
}
Example #3
0
func Test_FinderNode_Expand_single_include(t *testing.T) {
	cleanup := testutils.Chtemp()
	defer cleanup()

	testutils.TouchFiles(
		"lib1/foo.c", "lib1/sub/blah.c", "include/bop.h", "include/bip.h")

	finder := NewFinderNode("*/*.c")
	assertExpand(t, nil, []string{"lib1/foo.c"}, finder)

	finder = NewFinderNode("**/*.c")
	assertExpand(t, nil, []string{"lib1/foo.c", "lib1/sub/blah.c"}, finder)

	finder = NewFinderNode("l?b?/**/*.c")
	assertExpand(t, nil, []string{"lib1/foo.c", "lib1/sub/blah.c"}, finder)

	finder = NewFinderNode("in?lu?e/*.h")
	assertExpand(t, nil, []string{"include/bip.h", "include/bop.h"}, finder)

	finder = NewFinderNode("inc*/?i*.h")
	assertExpand(t, nil, []string{"include/bip.h"}, finder)

	// adding new files changes nothing, because FinderNode caches the
	// result of Expand()
	testutils.TouchFiles("include/mip.h", "include/fibbb.h")
	assertExpand(t, nil, []string{"include/bip.h"}, finder)

	// but a new FileFinder instance will see them
	finder = NewFinderNode("inc*/?i*.h")
	assertExpand(t,
		nil,
		[]string{"include/bip.h", "include/fibbb.h", "include/mip.h"},
		finder)
}
Example #4
0
func Test_FinderNode_Expand_vars(t *testing.T) {
	// imagine code like this:
	//   srcdir = "src/stuff"
	//   files = <$srcdir/*.c>
	//   "myapp": "$srcdir/main.c" {
	//       "cc -c $files"
	//   }
	// ...i.e. a FinderNode that is not in the DAG, so variable
	// references do not get expanded by DAG.ExpandNodes(). This is
	// clearly a bogus build script, but that's beside the point. We
	// need to ensure that the wildcard expanded is not "$srcdir/*.c"
	// but "src/stuff/*.c".

	cleanup := testutils.Chtemp()
	defer cleanup()

	testutils.TouchFiles(
		"lib1/foo.c", "lib1/sub/blah.c", "include/bop.h", "include/bip.h")

	ns := types.NewValueMap()
	ns.Assign("libsrc", types.MakeFuString("lib1"))
	finder := NewFinderNode("$libsrc/**/*.c")
	expect := []string{
		"lib1/foo.c",
		"lib1/sub/blah.c",
	}
	assertExpand(t, ns, expect, finder)
}
Example #5
0
func Test_FinderNode_FindFiles_prune(t *testing.T) {
	cleanup := testutils.Chtemp()
	defer cleanup()

	testutils.TouchFiles(
		"src/a/1.c", "src/a/2.c", "src/a/2.h", "src/a/3.h",
		"src/b/1.c", "src/b/2.c", "src/b/2.h", "src/b/3.h",
		"src/b/b/1.c", "src/b/b/2.c", "src/b/b/2.h",
		"lib/x.c", "lib/sub/x.c")

	var finder *FinderNode
	var expect []string

	test := func(expect []string) {
		actual, err := finder.FindFiles()
		assert.Nil(t, err)
		if !reflect.DeepEqual(expect, actual) {
			t.Errorf("includes = %v, prune = %v:\nexpected:\n%#v\nbut got:\n%#v",
				finder.includes, finder.prune, expect, actual)
		}
		// wipe the cache so this finder can be used again
		finder.matches = nil
	}

	finder = NewFinderNode("src/**/*.c", "src/b/**/*.h")
	finder.Prune("src/a")
	expect = []string{
		"src/b/1.c", "src/b/2.c", "src/b/b/1.c", "src/b/b/2.c",
		"src/b/2.h", "src/b/3.h", "src/b/b/2.h"}
	test(expect)

	// successive calls to Prune() build up the prune set
	finder = NewFinderNode("*/*.c")
	finder.Prune("src")
	expect = []string{"lib/x.c"}
	test(expect)
	finder.Prune("lib")
	expect = []string{}
	test(expect)

	finder = NewFinderNode("*/*/?.c")
	expect = []string{
		"lib/sub/x.c", "src/a/1.c", "src/a/2.c", "src/b/1.c", "src/b/2.c"}
	test(expect)
	finder.Prune("src/b")
	expect = []string{
		"lib/sub/x.c", "src/a/1.c", "src/a/2.c"}
	test(expect)

	finder = NewFinderNode("**/b/?.h")
	expect = []string{"src/b/2.h", "src/b/3.h", "src/b/b/2.h"}
	test(expect)
	finder.Prune("src/b/b")
	expect = []string{"src/b/2.h", "src/b/3.h"}
	test(expect)
	finder.Prune("src/b")
	expect = []string{}
	test(expect)
}
Example #6
0
func Test_mkdir(t *testing.T) {
	cleanup := testutils.Chtemp()
	defer cleanup()

	// mkdir() happily accepts an empty argument list, to allow for
	// cases where a user-defined list becomes the arg list, and it
	// just happens to be empty
	pargs := []types.FuObject{}
	args := RuntimeArgs{
		BasicArgs: types.MakeBasicArgs(nil, pargs, nil),
	}
	result, errs := fn_mkdir(args)
	assert.Nil(t, result)
	assert.Equal(t, 0, len(errs))
	assert.Equal(t, []string{}, dirContents("."))

	// easiest case: create a single dir
	pargs = types.MakeStringList("foo").List()
	args.SetArgs(pargs)
	result, errs = fn_mkdir(args)
	assert.Nil(t, result)
	assert.Equal(t, 0, len(errs))
	assert.Equal(t, []string{"foo"}, dirContents("."))
	assert.True(t, isDir("foo"))

	// create multiple dirs, including "foo" which already exists
	pargs = types.MakeStringList("meep/meep/meep", "foo", "meep/beep").List()
	args.SetArgs(pargs)
	result, errs = fn_mkdir(args)
	assert.Nil(t, result)
	assert.Equal(t, 0, len(errs))
	assert.Equal(t, []string{"foo", "meep"}, dirContents("."))
	assert.True(t, isDir("foo"))
	assert.True(t, isDir("meep/meep"))
	assert.True(t, isDir("meep/meep/meep"))
	assert.True(t, isDir("meep/beep"))

	// now with an error in the middle of the list (*but* we still
	// create the other requested dirs!)
	testutils.TouchFiles("meep/zap")
	pargs = types.MakeStringList("meep/bap", "meep/zap/zip", "foo/bar").List()
	args.SetArgs(pargs)
	result, errs = fn_mkdir(args)
	assert.Nil(t, result)
	assert.Equal(t, 1, len(errs))
	assert.Equal(t, "mkdir meep/zap: not a directory", errs[0].Error())
	assert.True(t, isDir("meep/bap"))
	assert.True(t, isDir("foo/bar"))

	// finally, with multiple errors
	pargs = append(pargs, types.MakeFuString("meep/zap/blop"))
	args.SetArgs(pargs)
	result, errs = fn_mkdir(args)
	assert.Nil(t, result)
	assert.Equal(t, 2, len(errs))
	assert.Equal(t, "mkdir meep/zap: not a directory", errs[0].Error())
	assert.Equal(t, "mkdir meep/zap: not a directory", errs[1].Error())
}
Example #7
0
func Test_println(t *testing.T) {
	cleanup1 := testutils.Chtemp()
	defer cleanup1()

	rfile, err := os.Create("stdout")
	if err != nil {
		panic(err)
	}

	// save a copy of stdout in another fd
	stdout_fd := int(os.Stdout.Fd())
	save_stdout, err := syscall.Dup(stdout_fd)
	if err != nil {
		panic(err)
	}

	// redirect stdout to rfile
	err = syscall.Dup2(int(rfile.Fd()), stdout_fd)
	if err != nil {
		panic(err)
	}

	cleanup2 := func() {
		rfile.Close()
		err = syscall.Dup2(save_stdout, stdout_fd)
		if err != nil {
			panic(err)
		}
		syscall.Close(save_stdout)
	}
	defer cleanup2()

	args := RuntimeArgs{
		BasicArgs: types.MakeBasicArgs(nil, []types.FuObject{}, nil),
	}

	result, errs := fn_println(args)
	assert.Nil(t, result)
	assert.Equal(t, 0, len(errs))
	data, err := ioutil.ReadFile("stdout")
	assert.Nil(t, err)
	assert.Equal(t, "\n", string(data))
	rfile.Truncate(0)
	rfile.Seek(0, 0)

	args.SetArgs(types.MakeStringList("hello", "world").List())
	fn_println(args)
	data, err = ioutil.ReadFile("stdout")
	assert.Nil(t, err)
	assert.Equal(t, "hello world\n", string(data))
	rfile.Truncate(0)
	rfile.Seek(0, 0)
}
Example #8
0
func Test_FinderNode_Add_Expand(t *testing.T) {
	cleanup := testutils.Chtemp()
	defer cleanup()
	testutils.TouchFiles(
		"src/foo.c", "src/foo.h", "main.c", "include/bop.h",
		"doc.txt", "doc/stuff.txt", "doc/blahblah.rst")

	finder1 := NewFinderNode("**/*.c")
	finder2 := NewFinderNode("doc/*.txt")

	// sum = <**/*.c> + <doc/*.txt>
	expect := []string{
		"main.c", "src/foo.c", "doc/stuff.txt"}
	sum, err := finder1.Add(finder2)
	assert.Nil(t, err)
	assertExpand(t, nil, expect, sum)

	// sum = sum + <"*c*/?o?.h">
	finder3 := NewFinderNode("*c*/?o?.h")
	expect = append(expect, "include/bop.h", "src/foo.h")
	sum, err = sum.Add(finder3)
	assert.Nil(t, err)
	assertExpand(t, nil, expect, sum)

	// sum = <*c*/?o?.h> + <**/*.c>
	expect = []string{
		"include/bop.h", "src/foo.h", "main.c", "src/foo.c"}
	sum, err = finder3.Add(finder1)
	assert.Nil(t, err)
	assertExpand(t, nil, expect, sum)

	// sum = <doc/*.txt> + sum
	// (effectively: sum = <doc/*.txt> + (<*c*/?o?.h> + <**/*.c>))
	expect = append([]string{"doc/stuff.txt"}, expect...)
	sum, err = finder2.Add(sum)
	assert.Nil(t, err)
	assertExpand(t, nil, expect, sum)

	// sum = <**/*.c> + "urgh"
	expect = []string{
		"main.c", "src/foo.c", "urgh"}
	sum, err = finder1.Add(types.MakeFuString("urgh"))
	assert.Nil(t, err)
	assertExpand(t, nil, expect, sum)

	// sum = <**/*.c> + ["a", "b", "c"]
	expect = []string{
		"main.c", "src/foo.c", "a", "b", "c"}
	list := types.MakeStringList("a", "b", "c")
	sum, err = finder1.Add(list)
	assert.Nil(t, err)
	assertExpand(t, nil, expect, sum)
}
Example #9
0
func Test_KyotoDB_lookup_fail(t *testing.T) {
	// looking up a non-existent key is not an error
	cleanup := testutils.Chtemp()
	defer cleanup()

	db, err := OpenKyotoDB("test.kch", true)
	if err != nil {
		t.Fatal(err)
	}
	record, err := db.LookupNode("nosuchnode")
	assert.Nil(t, record)
	assert.Nil(t, err)
}
Example #10
0
func Test_FileNode_Signature(t *testing.T) {
	cleanup := testutils.Chtemp()
	defer cleanup()

	testutils.Mkdirs("d1", "d2")
	testutils.Mkfile("d1", "empty", "")
	testutils.Mkfile("d2", "stuff", "foo\n")

	node1 := NewFileNode("d1/empty")
	node2 := NewFileNode("d2/stuff")
	node3 := NewFileNode("nonexistent")

	expect := []byte{}
	hash := fnv.New64a()
	assert.Equal(t, 8, hash.Size())
	expect = hash.Sum(expect)

	sig, err := node1.Signature()
	assert.Nil(t, err)
	assert.Equal(t, expect, sig)

	hash.Write([]byte{'f', 'o', 'o', '\n'})
	expect = expect[:0]
	expect = hash.Sum(expect)
	sig, err = node2.Signature()
	assert.Nil(t, err)
	assert.Equal(t, expect, sig)

	// make sure it's cached, i.e. changes to the file are not seen by
	// the same FileNode object in the same process
	testutils.Mkfile("d2", "stuff", "fooo\n")
	sig, err = node2.Signature()
	assert.Nil(t, err)
	assert.Equal(t, expect, sig)

	// in fact, even if the file disappears, we still have its signature
	err = os.Remove("d2/stuff")
	if err != nil {
		panic(err)
	}
	sig, err = node2.Signature()
	assert.Nil(t, err)
	assert.Equal(t, expect, sig)

	sig, err = node3.Signature()
	assert.NotNil(t, err)
	assert.Equal(t, "open nonexistent: no such file or directory", err.Error())
}
Example #11
0
func Test_findScripts(t *testing.T) {
	cleanup := testutils.Chtemp()
	defer cleanup()

	var script string
	var err error

	// case 1: user specifies the script to run, regardless of whether
	// it exists or not
	script, err = findScript("foo")
	assert.Equal(t, "foo", script)
	assert.Nil(t, err)

	// case 2: no *.fubsy files in current dir
	script, err = findScript("")
	assert.Equal(t,
		"main.fubsy not found (and no other *.fubsy files found)",
		err.Error())

	// case 3: only main.fubsy exists
	testutils.TouchFiles("main.fubsy")
	script, err = findScript("")
	assert.Equal(t, "main.fubsy", script)
	assert.Nil(t, err)

	// case 4: multiple *.fubsy files exist, including main.fubsy
	testutils.TouchFiles("a.fubsy", "b.fubsy")
	script, err = findScript("")
	assert.Equal(t, "main.fubsy", script)
	assert.Nil(t, err)

	// case 5: multiple *.fubsy files exist, not including main.fubsy
	remove("main.fubsy")
	script, err = findScript("")
	assert.Equal(t, "", script)
	assert.True(t,
		strings.HasPrefix(
			err.Error(),
			"main.fubsy not found, and multiple *.fubsy files exist",
		))

	// case 6: exactly one *.fubsy file exists, and it's not main.fubsy
	remove("a.fubsy")
	script, err = findScript("")
	assert.Equal(t, "b.fubsy", script)
	assert.Nil(t, err)
}
Example #12
0
func Test_remove(t *testing.T) {
	cleanup := testutils.Chtemp()
	defer cleanup()

	args := RuntimeArgs{
		BasicArgs: types.MakeBasicArgs(nil, []types.FuObject{}, nil),
	}

	// remove() doesn't care about empty arg list (same reason as mkdir())
	result, errs := fn_remove(args)
	assert.Nil(t, result)
	assert.Equal(t, 0, len(errs))

	// remove() ignores non-existent files
	args.SetArgs(types.MakeStringList("foo", "bar/bleep/meep", "qux").List())
	result, errs = fn_remove(args)
	assert.Nil(t, result)
	assert.Equal(t, 0, len(errs))

	// remove() removes regular files
	testutils.TouchFiles("foo", "bar/bleep/meep", "bar/bleep/feep", "qux")
	args.SetArgs(types.MakeStringList("foo", "bar/bleep/meep", "bogus").List())
	result, errs = fn_remove(args)
	assert.Nil(t, result)
	assert.Equal(t, 0, len(errs))
	assert.Equal(t, []string{"bar", "qux"}, dirContents("."))
	assert.Equal(t, []string{"bleep"}, dirContents("bar"))
	assert.Equal(t, []string{"feep"}, dirContents("bar/bleep"))

	// remove() removes files and directories too
	testutils.TouchFiles("foo", "bar/bleep/meep", "qux")
	args.SetArgs(types.MakeStringList("bogus", "bar", "morebogus", "qux").List())
	result, errs = fn_remove(args)
	assert.Nil(t, result)
	assert.Equal(t, 0, len(errs))
	assert.Equal(t, []string{"foo"}, dirContents("."))

	// remove() fails if it tries to delete from an unwriteable directory
	testutils.TouchFiles("bar/bong", "qux/bip")
	testutils.ChmodRO("bar")
	defer testutils.ChmodOwnerAll("bar")

	args.SetArgs(types.MakeStringList("bar", "qux").List())
	result, errs = fn_remove(args)
	assert.Nil(t, result)
	assert.Equal(t, "remove bar/bong: permission denied", errs[0].Error())
}
Example #13
0
func Test_FinderNode_Expand_empty(t *testing.T) {
	cleanup := testutils.Chtemp()
	defer cleanup()

	// no patterns, no files: of course we find nothing
	finder := &FinderNode{}
	assertExpand(t, nil, []string{}, finder)

	// patterns, but no files: still nothing
	finder.includes = []string{"**/*.c", "include/*.h", "*/*.txt"}
	assertExpand(t, nil, []string{}, finder)

	// no patterns, some files: still nothing
	finder.includes = finder.includes[0:0]
	testutils.TouchFiles(
		"lib1/foo.c", "lib1/sub/blah.c", "include/bop.h", "include/bip.h")
	assertExpand(t, nil, []string{}, finder)
}
Example #14
0
func Test_KyotoDB_basics(t *testing.T) {
	cleanup := testutils.Chtemp()
	defer cleanup()

	db, err := OpenKyotoDB("test1.kch", true)
	assert.NotNil(t, db.kcdb)
	if err != nil {
		t.Fatal(err)
	}

	rec1 := NewBuildRecord()
	rec1.SetTargetSignature([]byte{})
	err = db.WriteNode("node0", rec1)
	assert.Nil(t, err)
	rec2, err := db.LookupNode("node0")
	assert.Nil(t, err)
	assert.True(t, rec1.Equal(rec2))

	// Make sure it works across database close/reopen.
	rec1.AddParent("node1", []byte{34})
	rec1.AddParent("node2", []byte{54, 63})
	rec1.SetTargetSignature([]byte{200, 150, 100})

	enc, err := rec1.encode()
	assert.Nil(t, err)
	rec2 = NewBuildRecord()
	err = rec2.decode(enc)

	err = db.WriteNode("node0", rec1)
	assert.Nil(t, err)

	err = db.Close()
	assert.Nil(t, err)

	db, err = OpenKyotoDB("test1.kch", false) // open read-only
	if err != nil {
		t.Fatal(err)
	}
	rec2, err = db.LookupNode("node0")
	assert.Nil(t, err)
	assert.True(t, rec1.Equal(rec2),
		"wrote record:\n%#v\nand got back:\n%#v",
		rec1, rec2)
}
Example #15
0
func Test_FinderNode_Expand_double_recursion(t *testing.T) {
	// Java programmers love this sort of insanely deep structure, and
	// Ant supports patterns with multiple occurences of "**" ... so I
	// guess Fubsy has to support them too!
	cleanup := testutils.Chtemp()
	defer cleanup()

	testutils.TouchFiles(
		"app1/src/main/org/example/app1/App1.java",
		"app1/src/main/org/example/app1/Util.java",
		"app1/src/main/org/example/app1/doc.txt",
		"app1/src/main/org/example/app1/subpkg/Stuff.java",
		"app1/src/main/org/example/app1/subpkg/MoreStuff.java",
		"app1/src/test/org/example/app1/StuffTest.java",
		"misc/app2/src/main/org/example/app2/App2.java",
		"misc/app3/src/main/org/example/app3/App3.java",
		"misc/app3/src/main/org/example/app3/Helpers.java",
		"misc/app3/src/test/org/example/app3/TestHelpers.java",
		"misc/app3/src/test/org/example/app3/testdata",
	)

	var finder *FinderNode
	var expect []string
	finder = NewFinderNode("**/test/**/*.java")
	expect = []string{
		"app1/src/test/org/example/app1/StuffTest.java",
		"misc/app3/src/test/org/example/app3/TestHelpers.java",
	}
	assertExpand(t, nil, expect, finder)

	finder = NewFinderNode("**/test/**/*")
	expect = []string{
		"app1/src/test/org/example/app1/StuffTest.java",
		"misc/app3/src/test/org/example/app3/TestHelpers.java",
		"misc/app3/src/test/org/example/app3/testdata",
	}
	assertExpand(t, nil, expect, finder)

	finder = NewFinderNode("**/test/**")
	assertExpand(t, nil, expect, finder)
}
Example #16
0
func Test_FileNode_Changed(t *testing.T) {
	// this is really a test of Signature() + Changed() together, because
	// Changed() itself is so trivial that testing it is no challenge
	cleanup := testutils.Chtemp()
	defer cleanup()

	testutils.Mkfile(".", "stuff.txt", "blah blah blah\n")
	node := NewFileNode("stuff.txt")
	osig, err := node.Signature()
	assert.Nil(t, err)

	// construct a new FileNode so the cache is lost
	node = NewFileNode("stuff.txt")
	nsig, err := node.Signature()
	assert.Nil(t, err)
	assert.False(t, node.Changed(osig, nsig))

	// modify the file and repeat
	testutils.Mkfile(".", "stuff.txt", "blah blah blah\nblah")
	node = NewFileNode("stuff.txt")
	nsig, err = node.Signature()
	assert.Nil(t, err)
	assert.True(t, node.Changed(osig, nsig))
}
Example #17
0
func Test_FileNode_Exists(t *testing.T) {
	cleanup := testutils.Chtemp()
	defer cleanup()

	testutils.TouchFiles("foo.txt", "a/a/a/a/foo.txt", "a/b/unreadable")

	testutils.ChmodNoAccess("a/b")
	defer testutils.ChmodOwnerAll("a/b")

	dag := NewDAG()
	tests := []struct {
		name   string
		exists bool
		err    string
	}{
		{"foo.txt", true, ""},
		{"a/a/a", false, "stat a/a/a: is a directory, not a regular file"},
		{"a/a/a/bogus", false, ""},
		{"a/a/a/a/foo.txt", true, ""},
		{"a/b/unreadable", false, "stat a/b/unreadable: permission denied"},
	}

	for _, test := range tests {
		node := MakeFileNode(dag, test.name)
		exists, err := node.Exists()
		if test.err != "" {
			assert.NotNil(t, err)
			assert.Equal(t, test.err, err.Error())
		}
		if test.exists && !exists {
			t.Errorf("%v: expected Exists() true, got false", node)
		} else if !test.exists && exists {
			t.Errorf("%v: expected Exists() false, got true", node)
		}
	}
}
Example #18
0
func Test_KyotoDB_checkVersion(t *testing.T) {
	cleanup := testutils.Chtemp()
	defer cleanup()

	// brand-new empty DB with no version number in it
	db := KyotoDB{}
	db.kcdb = cabinet.New()
	filename := "test.kch"
	err := db.kcdb.Open(filename, cabinet.KCOWRITER|cabinet.KCOCREATE)
	if err != nil {
		t.Fatal(err)
	}

	// checkVersion() on an empty DB in read-only mode fails, because
	// it can neither read nor write the version number
	err = db.checkVersion(filename, false, 0, 0, 0)
	expect := "database test.kch has no version number"
	if err == nil || err.Error() != expect {
		t.Errorf("expected error %s, but got %v", expect, err)
	}

	// in write mode it's OK, and sets the version number in the file
	err = db.checkVersion(filename, true, 513, 513, 513)
	assert.Nil(t, err)
	versionkey := makekey(PREFIX_META, "version")
	val, err := db.kcdb.Get(versionkey)
	assert.Nil(t, err)
	assert.Equal(t, "\x00\x00\x02\x01", string(val))

	// once the version number is in the file, write mode doesn't
	// matter -- now it's down to comparing with the supported range
	// of versions
	err = db.checkVersion(filename, false, 513, 134, 231)
	expect = "database test.kch is from the future (database version = 513, but max supported version = 231)"
	if err == nil || err.Error() != expect {
		t.Errorf("expected error\n%s\nbut got\n%v", expect, err)
	}

	err = db.checkVersion(filename, false, 513, 534, 546)
	expect = "database test.kch is too old (database version = 513, but min supported version = 534)"
	if err == nil || err.Error() != expect {
		t.Errorf("expected error\n%s\nbut got\n%v", expect, err)
	}

	err = db.checkVersion(filename, false, 513, 512, 514)
	assert.Nil(t, err)
	err = db.checkVersion(filename, false, 513, 513, 513)
	assert.Nil(t, err)
	err = db.checkVersion(filename, false, 513, 512, 513)
	assert.Nil(t, err)
	err = db.checkVersion(filename, false, 513, 513, 514)
	assert.Nil(t, err)

	// corrupt version number
	err = db.kcdb.Set(versionkey, []byte{0, 0x43, 0})
	assert.Nil(t, err)
	err = db.checkVersion(filename, false, 513, 513, 513)
	expect = "database test.kch: unable to decode version number (004300)"
	if err == nil || err.Error() != expect {
		t.Errorf("expected error\n%s\nbut got\n%v", expect, err)
	}
}