Code Monkey home page Code Monkey logo

quicktest's Introduction

Go Reference Build Status

quicktest

go get github.com/frankban/quicktest@latest

Package quicktest provides a collection of Go helpers for writing tests.

Quicktest helpers can be easily integrated inside regular Go tests, for instance:

import qt "github.com/frankban/quicktest"

func TestFoo(t *testing.T) {
    t.Run("numbers", func(t *testing.T) {
        c := qt.New(t)
        numbers, err := somepackage.Numbers()
        c.Assert(err, qt.IsNil)
        c.Assert(numbers, qt.DeepEquals, []int{42, 47})
    })
    t.Run("bad wolf error", func(t *testing.T) {
        c := qt.New(t)
        numbers, err := somepackage.Numbers()
        c.Assert(err, qt.ErrorMatches, "bad wolf")
    })
    t.Run("nil", func(t *testing.T) {
        c := qt.New(t)
        got := somepackage.MaybeNil()
        c.Assert(got, qt.IsNil, qt.Commentf("value: %v", somepackage.Value))
    })
}

Assertions

An assertion looks like this, where qt.Equals could be replaced by any available checker. If the assertion fails, the underlying Fatal method is called to describe the error and abort the test.

c := qt.New(t)
c.Assert(someValue, qt.Equals, wantValue)

If you don’t want to abort on failure, use Check instead, which calls Error instead of Fatal:

c.Check(someValue, qt.Equals, wantValue)

For really short tests, the extra line for instantiating *qt.C can be avoided:

qt.Assert(t, someValue, qt.Equals, wantValue)
qt.Check(t, someValue, qt.Equals, wantValue)

The library provides some base checkers like Equals, DeepEquals, Matches, ErrorMatches, IsNil and others. More can be added by implementing the Checker interface. Below, we list the checkers implemented by the package in alphabetical order.

All

All returns a Checker that uses the given checker to check elements of slice or array or the values of a map. It succeeds if all elements pass the check. On failure it prints the error from the first index that failed.

For example:

c.Assert([]int{3, 5, 8}, qt.All(qt.Not(qt.Equals)), 0)
c.Assert([][]string{{"a", "b"}, {"a", "b"}}, qt.All(qt.DeepEquals), []string{"c", "d"})

See also Any and Contains.

Any

Any returns a Checker that uses the given checker to check elements of a slice or array or the values from a map. It succeeds if any element passes the check.

For example:

c.Assert([]int{3,5,7,99}, qt.Any(qt.Equals), 7)
c.Assert([][]string{{"a", "b"}, {"c", "d"}}, qt.Any(qt.DeepEquals), []string{"c", "d"})

See also All and Contains.

CmpEquals

CmpEquals checks equality of two arbitrary values according to the provided compare options. DeepEquals is more commonly used when no compare options are required.

Example calls:

c.Assert(list, qt.CmpEquals(cmpopts.SortSlices), []int{42, 47})
c.Assert(got, qt.CmpEquals(), []int{42, 47}) // Same as qt.DeepEquals.

CodecEquals

CodecEquals returns a checker that checks for codec value equivalence.

func CodecEquals(
    marshal func(interface{}) ([]byte, error),
    unmarshal func([]byte, interface{}) error,
    opts ...cmp.Option,
) Checker

It expects two arguments: a byte slice or a string containing some codec-marshaled data, and a Go value.

It uses unmarshal to unmarshal the data into an interface{} value. It marshals the Go value using marshal, then unmarshals the result into an interface{} value.

It then checks that the two interface{} values are deep-equal to one another, using CmpEquals(opts) to perform the check.

See JSONEquals for an example of this in use.

Contains

Contains checks that a map, slice, array or string contains a value. It's the same as using Any(Equals), except that it has a special case for strings - if the first argument is a string, the second argument must also be a string and strings.Contains will be used.

For example:

c.Assert("hello world", qt.Contains, "world")
c.Assert([]int{3,5,7,99}, qt.Contains, 7)

ContentEquals

ContentEquals is is like DeepEquals but any slices in the compared values will be sorted before being compared.

For example:

c.Assert([]string{"c", "a", "b"}, qt.ContentEquals, []string{"a", "b", "c"})

DeepEquals

DeepEquals checks that two arbitrary values are deeply equal. The comparison is done using the github.com/google/go-cmp/cmp package. When comparing structs, by default no exported fields are allowed. If a more sophisticated comparison is required, use CmpEquals (see below).

Example call:

c.Assert(got, qt.DeepEquals, []int{42, 47})

Equals

Equals checks that two values are equal, as compared with Go's == operator.

For instance:

c.Assert(answer, qt.Equals, 42)

Note that the following will fail:

c.Assert((*sometype)(nil), qt.Equals, nil)

Use the IsNil checker below for this kind of nil check.

ErrorAs

ErrorAs checks that the error is or wraps a specific error type. If so, it assigns it to the provided pointer. This is analogous to calling errors.As.

For instance:

// Checking for a specific error type
c.Assert(err, qt.ErrorAs, new(*os.PathError))

// Checking fields on a specific error type
var pathError *os.PathError
if c.Check(err, qt.ErrorAs, &pathError) {
    c.Assert(pathError.Path, Equals, "some_path")
}

ErrorIs

ErrorIs checks that the error is or wraps a specific error value. This is analogous to calling errors.Is.

For instance:

c.Assert(err, qt.ErrorIs, os.ErrNotExist)

ErrorMatches

ErrorMatches checks that the provided value is an error whose message matches the provided regular expression.

For instance:

c.Assert(err, qt.ErrorMatches, `bad wolf .*`)

HasLen

HasLen checks that the provided value has the given length.

For instance:

c.Assert([]int{42, 47}, qt.HasLen, 2)
c.Assert(myMap, qt.HasLen, 42)

Implements

Implements checks that the provided value implements an interface. The interface is specified with a pointer to an interface variable.

For instance:

var rc io.ReadCloser
c.Assert(myReader, qt.Implements, &rc)

IsFalse

IsFalse checks that the provided value is false. The value must have a boolean underlying type.

For instance:

c.Assert(false, qt.IsFalse)
c.Assert(IsValid(), qt.IsFalse)

IsNil

IsNil checks that the provided value is nil.

For instance:

c.Assert(got, qt.IsNil)

As a special case, if the value is nil but implements the error interface, it is still considered to be non-nil. This means that IsNil will fail on an error value that happens to have an underlying nil value, because that's invariably a mistake. See https://golang.org/doc/faq#nil_error.

So it's just fine to check an error like this:

c.Assert(err, qt.IsNil)

IsNotNil

IsNotNil is a Checker checking that the provided value is not nil. IsNotNil is the equivalent of qt.Not(qt.IsNil)

For instance:

c.Assert(got, qt.IsNotNil)

IsTrue

IsTrue checks that the provided value is true. The value must have a boolean underlying type.

For instance:

c.Assert(true, qt.IsTrue)
c.Assert(myBoolean(false), qt.IsTrue)

JSONEquals

JSONEquals checks whether a byte slice or string is JSON-equivalent to a Go value. See CodecEquals for more information.

It uses DeepEquals to do the comparison. If a more sophisticated comparison is required, use CodecEquals directly.

For instance:

c.Assert(`{"First": 47.11}`, qt.JSONEquals, &MyStruct{First: 47.11})

Matches

Matches checks that a string or result of calling the String method (if the value implements fmt.Stringer) matches the provided regular expression.

For instance:

c.Assert("these are the voyages", qt.Matches, `these are .*`)
c.Assert(net.ParseIP("1.2.3.4"), qt.Matches, `1.*`)

Not

Not returns a Checker negating the given Checker.

For instance:

c.Assert(got, qt.Not(qt.IsNil))
c.Assert(answer, qt.Not(qt.Equals), 42)

PanicMatches

PanicMatches checks that the provided function panics with a message matching the provided regular expression.

For instance:

c.Assert(func() {panic("bad wolf ...")}, qt.PanicMatches, `bad wolf .*`)

Satisfies

Satisfies checks that the provided value, when used as argument of the provided predicate function, causes the function to return true. The function must be of type func(T) bool, having got assignable to T.

For instance:

// Check that an error from os.Open satisfies os.IsNotExist.
c.Assert(err, qt.Satisfies, os.IsNotExist)

// Check that a floating point number is a not-a-number.
c.Assert(f, qt.Satisfies, math.IsNaN)

Deferred Execution

The testing.TB.Cleanup helper provides the ability to defer the execution of functions that will be run when the test completes. This is often useful for creating OS-level resources such as temporary directories (see c.Mkdir).

When targeting Go versions that don't have Cleanup (< 1.14), the same can be achieved using c.Defer. In this case, to trigger the deferred behavior, calling c.Done is required. For instance, if you create a *C instance at the top level, you’ll have to add a defer to trigger the cleanups at the end of the test:

defer c.Done()

However, if you use quicktest to create a subtest, Done will be called automatically at the end of that subtest. For example:

func TestFoo(t *testing.T) {
    c := qt.New(t)
    c.Run("subtest", func(c *qt.C) {
        c.Setenv("HOME", c.Mkdir())
        // Here $HOME is set the path to a newly created directory.
        // At the end of the test the directory will be removed
        // and HOME set back to its original value.
    })
}

The c.Patch, c.Setenv, c.Unsetenv and c.Mkdir helpers use t.Cleanup for cleaning up resources when available, and fall back to Defer otherwise.

For a complete API reference, see the package documentation.

quicktest's People

Contributors

anthonyfok avatar bep avatar dependabot[bot] avatar dolmen avatar dsnet avatar frankban avatar gilcrest avatar kolyshkin avatar komuw avatar lmb avatar mhilton avatar miquella avatar mvdan avatar nagesh-ibm-power avatar pajlada avatar rogpeppe avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

quicktest's Issues

confusing failure when using qt.Equals with pointers

https://go.dev/play/p/vYkG-W6-tI2

package main

import (
	"testing"

	qt "github.com/frankban/quicktest"
)

type T int

func TestFun(t *testing.T) {
	c := qt.New(t)
	a := new(T)
	b := new(T)
	c.Assert(a, qt.Equals, b)
}

Result:

 error:
          values are not equal
got:
          &main.T(0)
want:
          <same as "got">

That provided a needed bit of levity in my day, but it took me a while to figure out that the problem was that the two pointers were distinct. (This was made worse in my case by a String function that printed the complete internal state of the struct, removing the telltail & at the beginning.)

I'd suggest printing something like %p %s for got/want when they are both pointers. That'd make it much more obvious.

Consider API redesign with the current testing package

I realise that this module is a v1 and we cannot break backwards compatibility, which is a good thing. However, the testing package has evolved quite a bit in the past few years - especially with methods like TempDir and Cleanup, which came directly from this module :)

I'm starting to wonder if designing a v2 would be a good idea. The simplest changes we could make are removing deprecated APIs now available in the upstream package, such as Mkdir, Defer, and Done. I imagine there might also be some other tech debt that we could fix when given the chance to break backwards compatibility.

I also wonder if we could remove the New constructor entirely, and replace methods like C.Check with qt.Check. For example, instead of:

func TestFoo(t *testing.T) {
    c := qt.New(t)
    numbers, err := somepackage.Numbers()
    c.Assert(numbers, qt.DeepEquals, []int{42, 47})
    c.Assert(err, qt.ErrorMatches, "bad wolf")
}

We could just do:

func TestFoo(t *testing.T) {
    numbers, err := somepackage.Numbers()
    qt.Assert(t, numbers, qt.DeepEquals, []int{42, 47})
    qt.Assert(t, err, qt.ErrorMatches, "bad wolf")
}

Presumably, part of the reason a state was needed was for Defer/Done, which should no longer apply. It seems like the only other reason to keep state around is C.SetFormat. I hope that we have alternative options there, such as a global per-package format, or deriving the format from the test name, or globally registering a format for a specific *testing.T like qt.SetFormat(t, ...).

/cc @rogpeppe who I think has been thinking about a possible v2 as well

LGPL license in checker.go

You reference an LGPLv3 license in checker.go, doesn't that mean that anyone using this code is required to follow that license if using your code? The code in question seems very simple, why not rewrite it to avoid the licensing issues that will surely result from using this code?

func (c *satisfiesChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) {
// Original code at
// https://github.com/juju/testing/blob/master/checkers/bool.go.
// Copyright 2011 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.

Note that there is nothing in the LICENSE file about an LGPL license being in effect?

Add a warning that this package is deprecated in favor of go-quicktest/qt

https://github.com/go-quicktest/qt is stable and uses a generic API, which has fewer footguns than the API in this package.

Moreover, it's under the go-quicktest github org, which is better in the long term.

I think the README should suggest that users consider switching over, and say that this module is not getting any further feature work.

Also, it might be useful to provide some way to rewrite existing frankban/quicktest test code to go-quicktest/quicktest. Here is the sed monstrosity I hacked together in twenty minutes, which was enough to rewrite nearly all of my test code across five repos:

sed -r -i \
    -e 's@qt "github\.com/frankban/quicktest"@"github.com/go-quicktest/qt"@' \
	-e 's@\(([tbf]), (.+), (qt\.Is[a-zA-Z]+)\b@(\1, \3(\2)@' \
	-e 's@\(([tbf]), (.+), (qt\.[a-zA-Z().]+), ([^,]+)@(\1, \3(\2, \4)@' \
	$(git ls-files '*.go')
go mod tidy

format uint64 differently.

Right now, if you have code like;

c := qt.New(t)
c.Assert(uint64(17), qt.Equals, 8)

See: https://play.golang.org/p/MV6g6URN_Gy

It results in

=== RUN   TestLastIndex
    prog.go:11: 
        error:
          values are not equal
        got:
          uint64(0x11)
        want:
          int(8)
        stack:
          /tmp/sandbox759567241/prog.go:11
            <cannot parse source file: open /tmp/sandbox759567241/prog.go: no such file or directory>
        
--- FAIL: TestLastIndex (0.00s)

In my opinion, formatting it as uint64(17) instead of the current uint64(0x11) would be much better

Panic on <nil>.String()

This:

package main

import (
	"testing"

	qt "github.com/frankban/quicktest"
)

type B struct {
	foo string
}

func (b *B) Foo() string {
	return b.foo
}

func (b *B) String() string {
	return b.Foo()
}

func TestNil(t *testing.T) {
	c := qt.New(t)

	var a *B

	c.Assert(a, qt.Not(qt.IsNil))
}

Panics with:

--- FAIL: TestNil (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
	panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x1189ca6]

goroutine 18 [running]:
testing.tRunner.func1(0xc00010e100)
	/usr/local/go/src/testing/testing.go:830 +0x392
panic(0x11bc260, 0x13736b0)
	/usr/local/go/src/runtime/panic.go:522 +0x1b5
github.com/bep/temp.(*B).Foo(...)
	/Users/bep/dev/go/bep/temp/main_test.go:14
github.com/bep/temp.(*B).String(0x0, 0x11bc5e0, 0x0)
	/Users/bep/dev/go/bep/temp/main_test.go:18 +0x6
github.com/frankban/quicktest.Format(0x11bc5e0, 0x0, 0xc000074808, 0x1)
	/Users/bep/go/pkg/mod/github.com/frankban/[email protected]/format.go:20 +0x1b4
github.com/frankban/quicktest.writeError.func1(0x11f226d, 0x3, 0x11bc5e0, 0x0)
	/Users/bep/go/pkg/mod/github.com/frankban/[email protected]/report.go:61 +0x3f7
github.com/frankban/quicktest.writeError(0x122c920, 0xc00008a4b0, 0x122c980, 0xc0000883a0, 0x13735d0, 0x1, 0x1, 0x11bc5e0, 0x0, 0x0, ...)
	/Users/bep/go/pkg/mod/github.com/frankban/[email protected]/report.go:93 +0x3b9
github.com/frankban/quicktest.report(0x122c980, 0xc0000883a0, 0x13735d0, 0x1, 0x1, 0x11bc5e0, 0x0, 0x0, 0x0, 0x0, ...)
	/Users/bep/go/pkg/mod/github.com/frankban/[email protected]/report.go:46 +0xbe
github.com/frankban/quicktest.(*C).check(0xc00008a480, 0xc0000b1f10, 0x122d700, 0xc000088380, 0x11bc5e0, 0x0, 0x0, 0x0, 0x0, 0x10)
	/Users/bep/go/pkg/mod/github.com/frankban/[email protected]/quicktest.go:288 +0x931
github.com/frankban/quicktest.(*C).Assert(0xc00008a480, 0x11bc5e0, 0x0, 0x122d700, 0xc000088380, 0x0, 0x0, 0x0, 0x10745c6)
	/Users/bep/go/pkg/mod/github.com/frankban/[email protected]/quicktest.go:151 +0xc4
github.com/bep/temp.TestNil(0xc00010e100)
	/Users/bep/dev/go/bep/temp/main_test.go:26 +0xe9
testing.tRunner(0xc00010e100, 0x11fd218)
	/usr/local/go/src/testing/testing.go:865 +0xc0
created by testing.(*T).Run
	/usr/local/go/src/testing/testing.go:916 +0x35a
exit status 2
FAIL	github.com/bep/temp	0.011s
Error: process exited with code 1.

I should probably have implement String() more robustly, but I don't think that is very uncommon.

assert: additional argument are not treated as comments

The documentation for Assert has this bit in it:

Additional args (not consumed by the checker), when provided, are included as comments in the failure output when the check fails. 

I read this to mean that the following is legitimate:

qt.Assert(t, true, qt.Equals, false, "yeah, no")

However, the output is the following:

        error:
          bad check: too many arguments provided to checker: got 2, want 1
        got args:
          []interface {}{
              bool(false),
              "yeah, no",
          }
        want args:
          want
        stack:

Only using qt.Commentf as the last parameter is accepted. Is this intentional? I would find being able to pass a plain string (maybe even strings?) quite useful, since it reduces the burden of adding comments to asserts. Most asserts we have don't carry comments for this reason.

Improve the checker interface

Maybe something like:

// Checker is implemented by types used as part of Check/Assert invocations.
type Checker interface {
        // Check performs the check and returns an error in the case it fails.
        // The check is performed using the provided got argument and any
        // additional args required.
        Check(got interface{}, args []interface{}) error
        // Info returns the name of the checker and labels describing all required 
        // arguments, including the mandatory got argument and any additional 
        // required args.
        Info() (name string, argNames []string)
}

Then we can have a generic implementation for Not and also a generic way for outputting got/want args in case the concrete checker doesn't need any special handling when generating the error.

quicktest.Assert panics when called in a testscript command

When quickest.Assert is called on a failing comparison within a custom command in testscript, it panics. By contrast, when using testscript.Fatalf, the test exits regularly.

How to reproduce:

// go.mod
module testscript-explore

go 1.18

require (
	github.com/frankban/quicktest v1.14.3
	github.com/rogpeppe/go-internal v1.8.2-0.20220706194532-9d15b660d1d6
)
// qt-integration_test.go
package main

import (
	"testing"

	qt "github.com/frankban/quicktest"
	"github.com/rogpeppe/go-internal/testscript"
)

func TestQtIntegration(t *testing.T) {
	testscript.Run(t, testscript.Params{
		Dir: "testdata",
		Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){
			"wants_two_args": func(ts *testscript.TestScript, neg bool, args []string) {
				c := qt.New(t)
				c.Assert(len(args), qt.Equals, 2)
				c.Assert(args[0], qt.Equals, args[1])
				if len(args) < 2 {
					ts.Fatalf("needs two arguments")
				}
				if args[0] != args[1] {
					ts.Fatalf("want: %s - got %s", args[0], args[1])
				}
			},
		},
	})
}
# testdata/qt-integration.txt
env other=one

# this one passes
wants_two_args one $other

# this one fails
wants_two_args

sample run

$ go test
--- FAIL: TestQtIntegration (0.00s)
    qt-bug_test.go:16:
        error:
          values are not equal
        got:
          int(0)
        want:
          int(2)
        stack:
          $workdir/testscript-explore/qt-bug/qt-bug_test.go:16
            c.Assert(len(args), qt.Equals, 2)
        $HOME/go/pkg/mod/github.com/rogpeppe/[email protected]/testscript/testscript.go:527
            cmd(ts, neg, args[1:])
          $HOME/go/pkg/mod/github.com/rogpeppe/[email protected]/testscript/testscript.go:271
            ts.run()
         $HOME/go/pkg/mod/github.com/rogpeppe/[email protected]/testscript/testscript.go:198
            f(tshim{t})

    --- FAIL: TestQtIntegration/qt-integration (0.00s)
        testscript.go:428:
            # this one passes (0.000s)
            # this one fails (0.001s)
            > wants_two_args

        testing.go:1336: test executed panic(nil) or runtime.Goexit: subtest may have called FailNow on a parent test
FAIL
exit status 1
FAIL	testscript-explore/qt-bug	0.422s

After commenting out the quickest lines (15 to 17), the failure doesn't include a panic.

go test
--- FAIL: TestQtIntegration (0.00s)
    --- FAIL: TestQtIntegration/qt-integration (0.00s)
        testscript.go:428:
            # this one passes (0.000s)
            # this one fails (0.000s)
            > wants_two_args
            FAIL: testdata/qt-integration.txt:7: needs two arguments

FAIL
exit status 1
FAIL	testscript-explore/qt-bug	0.567s

I don't know if this is a bug or an incorrect usage from my side.
I'd like it to know how to address it, as I plan to use quickest in a larger project where this kind of integration is quite common (See this blog post for more detail.)

A little feedback

Few notes from porting a testify/assert module to quicktest:

  1. quicktest introduces a boilerplate line where I have to create a New checker for each test. This is great for not having to add the parameter to each assert call, but it also makes me write a new line for each test that has no connection to what I'm actually testing. This is a design decision because it has pros and cons and I have mixed opinions on this - but leaning towards testify/assert having the better design in this case.
  2. I was missing a qt.Contains similar to assert.Contains. I don't like to rewrite every Contains call as a regex as they are harder to read.

While I don't expect any changes to 1) because it's probably something you already agreed on, I might be able to send a PR for 2) if this fits your vision of the module.

stack trace's report of function+args can be unreasonably long for closures

The stack traces produced by quicktest are extremely awesome.

However, I've found a scenario where they can be verbose in a way that's probably a bug: inline function definitions.

It's probably easiest to just describe this by example:

    stack:
      /file/system/foo.go:40
        qt.Assert(t, err, qt.IsNotNil)
      /file/system/bar.go:14
        fn(tmpdir)
      /file/system/baz.go:16
        WithTmpdir("test-foobar-", func(tmpdir string) {
        	// ... and at this point it proceeds to dump the entire body of the function... 
        	// ... all the way to the ultimate closing paren!
        })

Now arguably I'm a monster for having such long inline function declarations that I actually notice this :) But still, I think it would be nice if some kind of elision threshold would exist and be applied by default here.

proposal: add Try and Recover

Error handling is tedious, and when writing tests we are prioritizing getting the test written quickly rather than necessarily making it the most robust piece of code we ever wrote.

I propose adding the following:

func (c *C) Recover()
func Try(error)
func Try1[A any](A, error) A
func Try2[A, B any](A, B, error) (A, B)
func Try3[A, B, C any](A, B, C, error) (A, B, C)
func Try4[A, B, C, D any](A, B, C, D, error) (A, B, C, D)

where the Try functions panic if err is non-nil. Otherwise, it peels off the error and returns the other arguments as is.
The TryN functions are defined such that N is the number of return arguments. Generics in Go do not have variadic parametric types, so we cannot express a generic function with a generic number of arguments. Try4 should be sufficient since no standard library function has more than 4 return arguments.

Example usage:

func Test(t *testing.T) {
    c := qt.New(t)
    defer c.Recover() // catch errors panicked by calls to `Try` below

    zr := qt.Try1(gzip.NewReader(...))
    b := qt.Try1(io.ReadAll(zr))
    ... = b
}

checkers aren't obvious in the documentation

The godoc.org site doesn't show variables with any prominence, so all the checkers that are defined as variables are unclear in the quicktest godoc page, which makes it harder than it should be to see what checkers are available.

Perhaps we should mention them explicitly in the package-level docs.

Add ErrorIs and ErrorAs checkers

Although I'm happy to spend the time writing up the pull request, I first wanted to check whether these proposed interfaces sound reasonable and might be accepted.

Several times now I've found myself wishing for a semantic convenience wrapper around errors.Is and errors.As. These have been times where ErrorMatches wouldn't suffice, as I need to know that the specific error is found somewhere in the wrapped error chain (i.e. by calling errors.Is).

The existing checks I use look something like:

c.Assert(errors.Is(err, ErrSomethingBad), qt.IsTrue)

var errDetails *ErrorDetails
if c.Check(errors.As(err, &errDetails), qt.IsTrue) {
	// …
}

The proposed interface would change the checks to look something like:

c.Assert(err, qt.ErrorIs(ErrSomethingBad))

var errDetails *ErrorDetails
if c.Check(err, qt.ErrorAs(&errDetails)) {
	// …
}

proposal: `Eventually` checker

Here's an idea for an API to make it easier to wait for a given condition to become true:

package quicktest

import "github.com/rogpeppe/retry"

// Eventually calls its provided function repeatedly,
// passing its returned value to the checker c until the checker
// succeeds.
//
// The retry argument governs how often the function is
// called; if it's nil, the default strategy is to use an exponential
// backoff that times out after about 5s.
//
// For example:
//
//	qt.Assert(t, func() int64 {
//		return atomic.LoadInt64(&foo)
//	}, qt.Eventually(qt.Equals, nil), int64(1234))
func Eventually(c Checker, retry *retry.Strategy) Checker

// EventuallyStable is like Eventually except that it also runs the checker
// for a time after the checker succeeds to make sure that it doesn't
// fail again. The stableRetry argument governs how that is done;
// the default is to run for about 100ms.
//
// In general stableRetry should complete in a much shorter
// period of time than retry because it will always be run to completion.
func EventuallyStable(c Checker, retry, stableRetry *retry.Strategy) Checker

Test failures on latest release

It was working with Go 1.15 beta 1 and kr/pretty 0.1.0, stopped working with Go 1.15 rc1 and kr/pretty 0.2.0.

I have then built the latest kr/pretty and latest quicktest with Go 1.15 final and it is still not working:

The error is as follow:

Testing    in: /builddir/build/BUILD/quicktest-1.10.1/_build/src
         PATH: /builddir/build/BUILD/quicktest-1.10.1/_build/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/sbin
       GOPATH: /builddir/build/BUILD/quicktest-1.10.1/_build:/usr/share/gocode
  GO111MODULE: off
      command: go test -buildmode pie -compiler gc -ldflags " -X github.com/frankban/quicktest/version=1.10.1 -extldflags '-Wl,-z,relro -Wl,--as-needed  -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld  '"
      testing: github.com/frankban/quicktest
github.com/frankban/quicktest
--- FAIL: TestCmpReportOutput (0.00s)
    report_test.go:160: failure:
        "\nerror:\n  values are not deep equal\ndiff (-got +want):\n    []*quicktest_test.reportExample{\n            &{\n                    AnInt:  42,\n  -                 ASlice: []string{},\n  +                 ASlice: nil,\n            },\n            &{AnInt: 47, ASlice: {\"these\", \"are\", \"the\", \"voyages\"}},\n  +         &{AnInt: 2},\n            &{AnInt: 1},\n  -         &{AnInt: 2},\n  +         &{ASlice: []string{\"foo\"}},\n            &{\n                    AnInt:  0,\n  -                 ASlice: []string{\"foo\", \"bar\"},\n  +                 ASlice: nil,\n            },\n    }\nstack:\n  /builddir/build/BUILD/quicktest-1.10.1/_build/src/github.com/frankban/quicktest/report_test.go:120\n    c.Assert(gotExamples, checker, wantExamples)\n"
        "\nerror:\n  values are not deep equal\ndiff (-got +want):\n    []*quicktest_test.reportExample{\n            &{\n                    AnInt:  42,\n  -                 ASlice: []string{},\n  +                 ASlice: nil,\n            },\n            &{AnInt: 47, ASlice: []string{\"these\", \"are\", \"the\", \"voyages\"}},\n  +         &{AnInt: 2},\n            &{AnInt: 1},\n  -         &{AnInt: 2},\n            &{\n                    AnInt: 0,\n                    ASlice: []string{\n                            \"foo\",\n  -                         \"bar\",\n                    },\n            },\n  +         &{},\n    }\nstack:\n  /builddir/build/BUILD/quicktest-1.10.1/_build/src/github.com/frankban/quicktest/report_test.go:120\n    c.Assert(gotExamples, checker, wantExamples)\n"
        ------------------------------ got ------------------------------
        
        error:
          values are not deep equal
        diff (-got +want):
            []*quicktest_test.reportExample{
                    &{
                            AnInt:  42,
          -                 ASlice: []string{},
          +                 ASlice: nil,
                    },
                    &{AnInt: 47, ASlice: {"these", "are", "the", "voyages"}},
          +         &{AnInt: 2},
                    &{AnInt: 1},
          -         &{AnInt: 2},
          +         &{ASlice: []string{"foo"}},
                    &{
                            AnInt:  0,
          -                 ASlice: []string{"foo", "bar"},
          +                 ASlice: nil,
                    },
            }
        stack:
          /builddir/build/BUILD/quicktest-1.10.1/_build/src/github.com/frankban/quicktest/report_test.go:120
            c.Assert(gotExamples, checker, wantExamples)
        ------------------------------ want -----------------------------
        
        error:
          values are not deep equal
        diff (-got +want):
            []*quicktest_test.reportExample{
                    &{
                            AnInt:  42,
          -                 ASlice: []string{},
          +                 ASlice: nil,
                    },
                    &{AnInt: 47, ASlice: []string{"these", "are", "the", "voyages"}},
          +         &{AnInt: 2},
                    &{AnInt: 1},
          -         &{AnInt: 2},
                    &{
                            AnInt: 0,
                            ASlice: []string{
                                    "foo",
          -                         "bar",
                            },
                    },
          +         &{},
            }
        stack:
          /builddir/build/BUILD/quicktest-1.10.1/_build/src/github.com/frankban/quicktest/report_test.go:120
            c.Assert(gotExamples, checker, wantExamples)
        -----------------------------------------------------------------
FAIL
exit status 1
FAIL	github.com/frankban/quicktest	0.701s

I don't know if the error is caused by Go 1.15 rc 1 update or kr/pretty one.

Do you have any insight about what is causing this?

Check value is one of a set of possibilities

I have the following check:

c.Check(err, qt.Any(qt.Equals), []error{nil, io.EOF})

I rushed into this assuming that it would test that it would pass if err matched any of nil and io.EOF. After reading the documentation I see that I got the operands reversed, but if I reverse them, the failure message is:

--- FAIL: TestShortFile (0.01s)
    quicktest.go:306: 
        error:
          no matching element found
        got:
          []error{
              nil,
              &errors.errorString{s:"EOF"},
          }
        want:
          e"unexpected EOF"
        stack:
          /Users/anacrolix/go/src/github.com/anacrolix/torrent/storage/file_test.go:41
            c.Check([]error{nil, io.EOF}, qt.Contains, err)

Which suggests that []error{nil, io.EOF} is expected to match err.

How can I check that err matches one of a list of values with the builtin checkers?

A few usage questions

Just started using this library today, and I really like it so far! It is a much tidier way to introduce some much needed asserts without a huge testing framework. I also love that it replaces some of the existing lifecycle management I had already been doing.
A few questions about the usage so far...

  1. I don't seem to understand how to customize the usage of DeepEquals / CmpEquals to match the usage of reflect.DeepEquals. I had some code that was checking equality on the *exec.Cmd type to ensure a command is generated the way it is expected. But when it runs through quicktest.DeepEquals, it fails because of unexported fields. I tried adding the cmpopt dependency and passing different combinations of options but I couldn't get it to compare this type the same way as the stdlib. Is there any more detailed usage examples on this?

  2. Am I overlooking a less verbose way to assert a bool value than doing c.Assert(val, qt.Equals, false)? Are there thoughts on having c.Assert(val, qt.True) and c.Assert(val, qt.False)?

Implement IsTrue / IsFalse checkers?

Is it worth introducing a c.Assert(v, qt.IsTrue) shortcut for c.Assert(v, qt.Equals, true)?
We always avoided shortcuts on the impression that usually the additional typing they save is not worth the price of having two different ways for expressing the same check.
On the other hand, IsTrue and IsFalse occur quite often in testing libraries and frameworks, and users expect they exist.

tests don't pass on master

I get these failures when running tests on master (commit 5af72c3):

--- FAIL: TestCheckers (0.01s)
    --- FAIL: TestCheckers/CmpEquals:_different_values (0.00s)
    	quicktest_test.go:214: prefix:
    		got  "\nvalues are not equal:\n(-got +want)\nroot.Ints[1->?]:\n\t-: 47\n\t+: <non-existent>\nchecker_test.go:561:\n        558       t.Run(test.about, func(t *testing.T) {\n        559         tt := &testingT{}\n        560         c := qt.New(tt)\n        561!        ok := c.Check(test.got, test.checker, test.args...)\n        562         checkResult(t, ok, tt.errorString(), test.expectedCheckFailure)\n        563       })\n        564       t.Run(\"Not \"+test.about, func(t *testing.T) {\n"
    		want "\nvalues are not equal:\n(-got +want)\nroot.Ints:\n\t-: []int{42, 47}\n\t+: []int{42}\n":
    		-----------------------------------
    		
    		values are not equal:
    		(-got +want)
    		root.Ints[1->?]:
    			-: 47
    			+: <non-existent>
    		checker_test.go:561:
    		        558       t.Run(test.about, func(t *testing.T) {
    		        559         tt := &testingT{}
    		        560         c := qt.New(tt)
    		        561!        ok := c.Check(test.got, test.checker, test.args...)
    		        562         checkResult(t, ok, tt.errorString(), test.expectedCheckFailure)
    		        563       })
    		        564       t.Run("Not "+test.about, func(t *testing.T) {
    --- FAIL: TestCheckers/CmpEquals:_different_values_with_options (0.00s)
    	quicktest_test.go:214: prefix:
    		got  "\nvalues are not equal:\n(-got +want)\nSort({[]int}).([]int)[2]:\n\t-: 4\n\t+: 3\nchecker_test.go:561:\n        558       t.Run(test.about, func(t *testing.T) {\n        559         tt := &testingT{}\n        560         c := qt.New(tt)\n        561!        ok := c.Check(test.got, test.checker, test.args...)\n        562         checkResult(t, ok, tt.errorString(), test.expectedCheckFailure)\n        563       })\n        564       t.Run(\"Not \"+test.about, func(t *testing.T) {\n"
    		want "\nvalues are not equal:\n(-got +want)\nSort({[]int})[2]:\n\t-: 4\n\t+: 3\n":
    		-----------------------------------
    		
    		values are not equal:
    		(-got +want)
    		Sort({[]int}).([]int)[2]:
    			-: 4
    			+: 3
    		checker_test.go:561:
    		        558       t.Run(test.about, func(t *testing.T) {
    		        559         tt := &testingT{}
    		        560         c := qt.New(tt)
    		        561!        ok := c.Check(test.got, test.checker, test.args...)
    		        562         checkResult(t, ok, tt.errorString(), test.expectedCheckFailure)
    		        563       })
    		        564       t.Run("Not "+test.about, func(t *testing.T) {
    --- FAIL: TestCheckers/DeepEquals:_different_values (0.00s)
    	quicktest_test.go:214: prefix:
    		got  "\nvalues are not equal:\n(-got +want)\n{[]int}[0]:\n\t-: 1\n\t+: 3\n{[]int}[2]:\n\t-: 3\n\t+: 1\nchecker_test.go:561:\n        558       t.Run(test.about, func(t *testing.T) {\n        559         tt := &testingT{}\n        560         c := qt.New(tt)\n        561!        ok := c.Check(test.got, test.checker, test.args...)\n        562         checkResult(t, ok, tt.errorString(), test.expectedCheckFailure)\n        563       })\n        564       t.Run(\"Not \"+test.about, func(t *testing.T) {\n"
    		want "\nvalues are not equal:\n(-got +want)\n{[]int}:\n\t-: []int{1, 2, 3}\n\t+: []int{3, 2, 1}\n":
    		-----------------------------------
    		
    		values are not equal:
    		(-got +want)
    		{[]int}[0]:
    			-: 1
    			+: 3
    		{[]int}[2]:
    			-: 3
    			+: 1
    		checker_test.go:561:
    		        558       t.Run(test.about, func(t *testing.T) {
    		        559         tt := &testingT{}
    		        560         c := qt.New(tt)
    		        561!        ok := c.Check(test.got, test.checker, test.args...)
    		        562         checkResult(t, ok, tt.errorString(), test.expectedCheckFailure)
    		        563       })
    		        564       t.Run("Not "+test.about, func(t *testing.T) {
FAIL
exit status 1
FAIL	github.com/frankban/quicktest	0.014s

More practical diff output

Currently, I may get something like this:

 quicktest.go:306: 
        error:
          values are not deep equal
        diff (-got +want):
            []string{
          - 	"a/b/c/f-1.txt",
          - 	"a/b/c/f-2.txt",
          - 	"a/b/c/f-3.txt",
          - 	"cf2/myfile.txt",
            }
        stack:
          /Users/bep/dev/go/gohugoio/hugo/hugofs/rootmapping_fs_test.go:397
            c.Assert(all, qt.DeepEquals, []string{})

Assertion errors like the above happen, in my experience, almost always only when you're creating the test itself:

  1. Add some dummy assertion value.
  2. Run the test and see that it fails.
  3. If the values printed are correct, put them in there and re-run the test.

Given the above, it would be much more useful to (also) have something like fmt.Printf("%#v", values) in the output so I could copy and paste it.

cmd/qtify

I’d love a source code rewriter that ports existing tests to quicktest. Could start with just the low-hanging fruit, the obvious cases. Bonus for also rewriting from testify. :)

With appropriate docs or a flag, it could even do slightly-semantic-changing things like err != nil to qt.IsNonNil.

add qt.IsNonNil

I just ported some tests to quicktest and it went pretty smoothly, but writing qt.Not(qt.IsNil)) felt really verbose. There’s IsTrue and IsFalse for the same reason, I presume.

name the package qt?

I was just thinking that the package layout in this module is a bit weird. I understand that we want the uses of the API to be short, so qt.Assert is nicer than quicktest.Assert. I also get that a module/repo path like github.com/frankban/qt would be a bit confusing.

That said, I think it's a bit unfortunate that we don't just publish the API under a package qt. This would be tricky to do in v1 without duplicating the entire API, but for v2 we could perhaps use something like github.com/frankban/quicktest/v2/qt. The import gets a tiny bit longer, but we avoid having to name the import. Plus, other tools like goimports and pkg.go.dev will work much better, as there will no longer be a mismatch between the actual package name and how it's used in code.

Does not work with Go 1.14 (Go master)

I'm starting to get test failures when testing with "Go tip":

 does not implement testing.TB (wrong type for Cleanup method)
		have Cleanup()
		want Cleanup(func())

TestCheckers/All_slice_mismatch_with_DeepEqual fails with "go test -v"

Greetings!

In the process of upgrading the Debian packaging of quicktest (https://tracker.debian.org/pkg/golang-github-frankban-quicktest) to v1.4.0, I came across the following test error:

    --- FAIL: TestCheckers/All_slice_mismatch_with_DeepEqual (0.00s)
        quicktest_test.go:621: prefix:
            got  "\nerror:\n  mismatch at index 1\nerror:\n  values are not deep equal\nfirst mismatched element:\n  []string{\"a\", \"c\"}\ndiff (-got +want):\n  \u00a0\u00a0[]string{\n  \u00a0\u00a0\t\"a\",\n  -\u00a0\t\"c\",\n  +\u00a0\t\"b\",\n  \u00a0\u00a0}\nstack:\n  /build/golang-github-frankban-quicktest-1.4.0/obj-x86_64-linux-gnu/src/github.com/frankban/quicktest/checker_test.go:2167\n    ok := c.Check(test.got, checker, test.args...)\n"
            want "\nerror:\n  mismatch at index 1\nerror:\n  values are not deep equal\ndiff (-got +want):\n  \u00a0\u00a0[]string{\n  \u00a0\u00a0\t\"a\",\n  -\u00a0\t\"c\",\n  +\u00a0\t\"b\",\n  \u00a0\u00a0}\nstack:\n"
            -------------------- got --------------------
            
            error:
              mismatch at index 1
            error:
              values are not deep equal
            first mismatched element:
              []string{"a", "c"}
            diff (-got +want):
                []string{
                        "a",
              -         "c",
              +         "b",
                }
            stack:
              /build/golang-github-frankban-quicktest-1.4.0/obj-x86_64-linux-gnu/src/github.com/frankban/quicktest/checker_test.go:2167
                ok := c.Check(test.got, checker, test.args...)
            
            -------------------- want -------------------
            
            error:
              mismatch at index 1
            error:
              values are not deep equal
            diff (-got +want):
                []string{
                        "a",
              -         "c",
              +         "b",
                }
            stack:
            
            ---------------------------------------------

and yet the Travis CI says all is fine.

It turns out that Debian tools are using go test -v whereas .travis.yml runs go test without the -v flag. Indeed, this is the very first time that I see that a go test result can change depending on whether it is in verbose mode or not. Interesting.

I could easily patch the Debian package to work around this issue, though I wonder if there is any easy way to allow TestCheckers/All_slice_mismatch_with_DeepEqual to run fine regardless of verbose or not? My quick attempt at setting verbose: false failed as it seems that the verbose setting is not passed down from qt.All() into nested checkers (based on my very limited understanding.)

Many thanks!

Anthony

display error output better

Sample output:

        quicktest.go:109: 
            error:
              values are not equal
            got:
              &errgo.Err{
                  Message_: "",
                  Cause_:   &errgo.Err{
                      Message_:    "duplicate key",
                      Cause_:      nil,
                      Underlying_: nil,
                      File:        "/home/rog/src/go/pkg/mod/github.com/juju/[email protected]/kv.go",
                      Line:        20,
                  },
                  Underlying_: &errgo.Err{
                      Message_:    "",
                      Cause_:      &errgo.Err{(CYCLIC REFERENCE)},
                      Underlying_: &errgo.Err{
                          Message_:    "key test-key already exists",
                          Cause_:      &errgo.Err{(CYCLIC REFERENCE)},
                          Underlying_: nil,
                          File:        "/home/rog/src/go/pkg/mod/github.com/juju/[email protected]/kv.go",
                          Line:        80,
                      },
                      File: "/home/rog/src/go/pkg/mod/github.com/juju/[email protected]/memsimplekv/keyvalue.go",
                      Line: 62,
                  },
                  File: "/home/rog/src/go/pkg/mod/github.com/juju/[email protected]/kv.go",
                  Line: 84,
              }
            want:
              nil
            sources:
              keyvalue.go:71:
                68     defer close()
                69   
                70     err = simplekv.SetKeyOnce(ctx, kv, "test-key", []byte("test-value"), time.Time{})
                71!    c.Assert(err, qt.Equals, nil)
                72   
                73     result, err := kv.Get(ctx, "test-key")
                74     c.Assert(err, qt.Equals, nil)

That's a very long error message, and most of it isn't very useful. Perhaps consider formatting errors differently?

Question: working with go-cmp.Options

Hello, I was just looking around cmp importers to see who else was using go-cmp and how they were working with go-cmp and I found this project.

It seems to have some similarities with the gotestyourself project I have been working on. (C.Mkdir -> fs.NewDir(), Assert/Check -> Assert/Check, C.Setenv -> env.Patch()).

The big differences seem to be

  1. quicktest has a focus on "lifecycle" (AddCleanup/Cleanup) which is not something that I've looked at. I considered a gotestyourself/suite that could be used as a drop in replacement of testify/suite to facilitate automated migration from testify, but I wasn't sure if it was worth adding something that was basically just a copy of the testify design. I really like the idea of C.Run() to automatically run AddCleanup.
  2. quicktest assertions seem to be modeled after gocheck, where as gotestyourself uses a Comparison type which is responsible for formatting the error message. Convenience functions are added to make the assert package similar to testify/require (with hopefully less interface bloat and more idiomatic naming).

So my questions:

  1. How have you found adoption of this package? Is it used internally in some projects or is it still being developed?
  2. What's your experience with creating go-cmp Option? AllowUnexported and IgnoreUnexported are pretty straightfoward. The other case I've found for them is to compare time.Time or time.Duration values that use time.Now(). For those cases I'm thinking of making a more convenient interface (example) but I'm not sure if I'm missing something that would make that interface unnecessary.

Omit package for types in current package

I really like it when I can just paste the output of got into my qt.DeepEquals expect. But if would be even better if the package names would be correct for types in the current package (which, when doing unit tests, isn't uncommon). One example from recently:

 page.PageMatcherParamsConfig{
                  Params: {
                      "foo": "bar",
                  },
                  Target: page.PageMatcher{Path:"", Kind:"page", Lang:"", Environment:""},
}

This test is run from package page. So, after I paste the above I need to remove the page. package "prefix".

This is not a big issue, but it would make quicktest even nicer.

confusing use of "unexpected success" when no errors are involved

https://go.dev/play/p/XftIYAoec8W

with v0.14.1, I see:

=== RUN   TestNil
    prog.go:11: 
        error:
          unexpected success
        got:
          (*int)(nil)
[...]

=== RUN   TestNonNil
    prog.go:16: 
        error:
          got non-nil value
        got:
          &int(0)
[...]

TestNonNil works fine, but TestNil tells me "unexpected success". I got that message in a long test and I was really confused - what was succeeding? The line in question wasn't comparing error values at all.

I think it should say "got nil value" if the type in question does not satisfy the error interface.

Not(DeepEquals) succeeds even though go-cmp panicked

The following test cases succeeds somewhat surprisingly:

func TestNotDeepEquals(t *testing.T) {
	type a struct {
		v int
	}

	v1 := a{1}
	qt.Assert(t, v1, qt.Not(qt.DeepEquals), v1)
}

Dropping the Not reveals that go-cmp actually returned an error:

        error:
          cannot handle unexported field at {testutils.a}.v:
          	"github.com/cilium/ebpf/internal/testutils".a
          consider using a custom Comparer; if you control the implementation of type, you can also consider using an Exporter, AllowUnexported, or cmpopts.IgnoreUnexported
        got:
          testutils.a{v:1}
        want:
          <same as "got">
        stack:
          /home/lorenz/dev/ebpf/internal/testutils/cmp_test.go:15
            qt.Assert(t, v1, qt.DeepEquals, v1)

Seems like Not treats an error from the checker which means "wasn't able to compare" the same as "not equal".

Print verbose output (-v) on failures

quicktest's output is much nicer when running with the -v flag, but it's not a great experience having that flag on all the time, and it's often a little fiddly to toggle it on and off in editors.

So, I would be happy if quicktest could make the verbose output the default on qt.Assert failures.

I was reminded about this by @rogpeppe 's fix here rogpeppe/go-internal#148 -- before that I always ran with the -v flag when using the testscripts package, which was annoying in general, because I only needed the verbose output when it failed.

Comparing uninitialized maps to nil

This looks like a bug:

var shouldBeNil map[string]interface{}

if shouldBeNil == nil {
	c.Log("Indeed nil, according to Go")
}

c.Assert(shouldBeNil, qt.Equals, nil)

It generates:

Indeed nil, according to Go

       error:
          values are not equal
        got:
          map[string]interface {}{}
        want:
          nil

Improve README

The README file is generated using godocdown, which is not maintained and doesn't support nice to have features like syntax highlighting of code blocks and table of contents.
There is a much more maintained library (https://github.com/posener/goreadme) that seems to support code highlighting, but lacks the templating features of godocdown.
Should we instead stop generating README from godocs, and prefer more curated markdown documentation?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.