-
Notifications
You must be signed in to change notification settings - Fork 951
Add test command to tinygo #243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Hi @carolynvs thank you very much for working on this. One initial comment without looking at anything, please use the You may have already seen it, but take a look at https://github.com/tinygo-org/tinygo/blob/master/CONTRIBUTING.md#how-to-use-our-github-repository for more info. I have taken the liberty of changing the branch for this PR to I will look at the content of this PR later today, thanks for getting us collectively started on this important set of features. |
src/testing/testing.go
Outdated
"_syscall.Exit", referenced from: | ||
_main in main.o | ||
ld: symbol(s) not found for architecture x86_64 | ||
clang: error: linker command failed with exit code 1 (use -v to see invocation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
syscall.Exit
is implemented in the runtime, as can be seen here:
The implementation is here:
It could be implemented in TinyGo in the following way in the runtime package:
//go:linkname exit exit
func exit(code int32)
//go:linkname syscall_Exit syscall.Exit
func syscall_Exit(cide int) {
exit(int32(code))
}
What this basically does is implement syscall.Exit
by calling the libc exit
function.
You could make a separate PR for this feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading it again, I see I made a mistake. The exit
declaration should be the following:
//go:export exit
func exit(code int32)
(//go:export
is a bit of a misnomer, we're importing a function here. But it lets the compiler know it should follow the C ABI).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to get this to work, and while there aren't any errors, it doesn't emit the specified exit code either. Maybe someone else can pick this up and get it to work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm maybe it should be //go:extern exit
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wait! I wasn't doing it right. I was testing it with tinygo run
but after trying it with tinygo build
then running the binary it created, that worked. So let me try more to get it to work with tinygo test
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I thought it didn't work so I wrote my own: #380 (sorry, didn't see your comment soon enough, don't want to cause duplicate work...)
And had the exact same realization. We should probably fix this, it makes logical sense that tinygo run
exits with the same exit code as the program you're trying to run. But that is somewhat of a separate issue.
Great to get this up! You haven't exactly chosen an easy feature to implement as a first thing. I see a few changes in this PR that would be easier to review and merge as separate PRs, as they are not directly related to the
|
I've found another interesting file: Regarding the testing itself:
This logic should likely live in the |
After rebasing on the
I am guessing that however I am building or running tinygo now that I'm on dev is no longer using the alternate source files in tinygo/src. So when it imports "testing", it is importing the real testing from go 1.11, which is then bringing in all those other packages. In tinygo/src/testing, it only imports fmt. Do you have advice on what has changed, or what I was taking advantage of on master that no longer works on dev that is causing the wrong files to be used? Sorry I'm not quite sure if I should be saying that the GOROOT is wrong? It's hard coded to /usr/local/go (which is where go 1.11 is installed on my machine) but that was what master was doing too so I'm a bit unsure of how to describe the problem. |
Actually, I think that's a good thing. Especially right now when lots of stuff changes.
Ah, yes, this was changed in #247. Standard library packages that must be overridden should now be explicitly listed here: Line 199 in a2d0f79
|
Thank you for the fast reply! That fixed it and I'm able to keep working on it. 👍 |
FYI, the go toolchain used to do this at the SSA level it looks like. In 1.12 they've completely redone it, but here's the code for 1.11 https://github.com/golang/tools/blob/release-branch.go1.11/go/ssa/testmain.go |
It's in the go/ssa package, but still they seem to produce regular Go code there: Very interesting how they make the test main package using a template. |
Now that v0.5.0 has been released, it would be great to get back to this PR. What do we need to do to support this? |
I actually got
Next step is to generate the TestMain if they don't have it (with the real signature), or call theirs, passing in the proper arguments if they do have one. Now that I have the right place in the loader, that is doable. I'll get what I have working so far pushed up so you can see and keep working on it. |
That's great! Maybe I can help with the TestMain generation? |
Oh shoot. At some point with the rebasing and updates/changes, I introduced a regression. When the code in the test files call a package, like |
Turns out my problem was just that I had rebased onto dev from a few weeks ago. I rebased to the most recent (post 0.5.0) and the regression went away. Phew! 😅 |
Got a bit further tonight. I've updated the call to TestMain to supply a populated test suite, so it's finally calling the proper TestMain signature Unfortunately, I've hit a snag. The same code that worked in the TestMain, when moved into the main function and passed into TestMain as an argument is causing things to not compile properly.
I've pushed up all the latest code in case that helps. I am assuming that the error I tried adding a explicit import to the package being tested and changing that line to |
loader/loader.go
Outdated
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
if compileTestBinary { | ||
p.SwapTestMain() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this returns an error that is not handled: error: invalid source
.
24776c8
to
ae344fb
Compare
cee1844
to
c5f2a9d
Compare
Ok, I hope enough of this is working now to merge and improve incrementally?
|
I have put these changes in the staging branch so that CircleCI will run all tests. |
I was able to get syscall.Exit to work, and have the exit code propagate from the test binary to |
} | ||
|
||
// Fatal is equivalent to Log followed by Fail | ||
func (t *T) Error(args ...interface{}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: the comment here has the wrong function name in it.
target.go
Outdated
@@ -301,6 +301,30 @@ func getGopath() string { | |||
return filepath.Join(home, "go") | |||
} | |||
|
|||
func getPackageRoot() (string, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the meantime, I wrote my own not realizing that you already had one here:
Line 319 in 421ef04
func getGoroot() string { |
I think it would be best to use that one instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh nevermind, getPackageRoot
does something different.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you try to resolve the package relative to GOPATH? Relative paths should just work (or there is a bug in the compiler), so in theory this function could be replaced with return "."
.
Symbol names are currently a bit ugly with that. Their import path should probably be replaced with just main
(so you get main.main
and not ugly stuff like ./src/examples/blinky1/blinky1.go.main
), but that is a separate issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will remove getPackageRoot and use "."
Can you run |
Fixed! |
Is there anything stopping us from squashing/merging this PR now? Seems like there is already enough here to be useful. |
When I rebased this branch and $ make tinygo-test
cd tests/tinygotest && tinygo test
# runtime
../../src/runtime/runtime_unix.go:22:6: exit redeclared in this block
../../src/runtime/runtime.go:111:6: other declaration of exit
../../src/runtime/runtime_unix.go:83:6: syscall_Exit redeclared in this block
../../src/runtime/runtime.go:114:6: other declaration of syscall_Exit
../../src/runtime/runtime_unix.go:84:7: cannot use code (variable of type int) as int32 value in argument to exit
Makefile:89: recipe for target 'tinygo-test' failed
make: *** [tinygo-test] Error 1 There are conflicting implementations of |
I commented the implementation of OK, then I went on to try to call some tests within the TinyGo drivers directory. I received the following error: $ tinygo test ./thermistor
could not determine the package root from the current directory "/home/ron/Development/tinygo/drivers" and gopath "/home/ron/.gvm/pkgsets/go1.12/global" The problem might be that I am using a symlink from my development directory to the actual gopath. I thought that we had that problem at one point, and it had had been resolved for |
loader/loader.go
Outdated
@@ -362,7 +479,7 @@ func (p *Package) importRecursively() error { | |||
if importedPkg.Importing { | |||
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]} | |||
} | |||
err = importedPkg.importRecursively() | |||
err = importedPkg.importRecursively(includeTests) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't this only apply to topmost packages?
Imported packages may also have tests with imports in the test files, but I don't think they should be included.
@@ -62,6 +66,11 @@ func (p *Program) Import(path, srcDir string) (*Package, error) { | |||
p.sorted = nil // invalidate the sorted order of packages | |||
pkg := p.newPackage(buildPkg) | |||
p.Packages[buildPkg.ImportPath] = pkg | |||
|
|||
if p.mainPkg == "" { | |||
p.mainPkg = buildPkg.ImportPath |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This implies that the first import sets the mainPkg
, which seems a bit unclean to me as it may be unexpected that the order of imports matters here (for example, things would be different if you imported the runtime package first). I think it would be more explicit if mainPkg
is exported and set in compiler/compiler.go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a change that could be made in a follow-up PR? It's not actually based on the import statement order. The compiler calls ldprogram.Import or ldprogram.ImportFile, depending on whether it is testing a package or a single go file, that is how the mainPkg is being set.
I'm feeling pressured to get this merged quickly (understandable, sorry it's taken so long) and as you can tell I don't have time to work on this anymore. I tried to export and set the package from the compiler, but it doesn't have all the information to set it. So I'd have to replicate logic from the loader and this would slow down getting the PR merged even more.
@@ -91,6 +100,11 @@ func (p *Program) ImportFile(path string) (*Package, error) { | |||
p.sorted = nil // invalidate the sorted order of packages | |||
pkg := p.newPackage(buildPkg) | |||
p.Packages[buildPkg.ImportPath] = pkg | |||
|
|||
if p.mainPkg == "" { | |||
p.mainPkg = buildPkg.ImportPath |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
target.go
Outdated
@@ -301,6 +301,30 @@ func getGopath() string { | |||
return filepath.Join(home, "go") | |||
} | |||
|
|||
func getPackageRoot() (string, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you try to resolve the package relative to GOPATH? Relative paths should just work (or there is a bug in the compiler), so in theory this function could be replaced with return "."
.
Symbol names are currently a bit ugly with that. Their import path should probably be replaced with just main
(so you get main.main
and not ugly stuff like ./src/examples/blinky1/blinky1.go.main
), but that is a separate issue.
main.go
Outdated
@@ -654,6 +680,14 @@ func main() { | |||
} | |||
err := Run(flag.Arg(0), *target, config) | |||
handleCompilerError(err) | |||
case "test": | |||
pkgRoot, err := getPackageRoot() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using the current directory is not unique to go test
: it is also supported by go install
, go build
, etc. and it would be useful to extend the support to these other commands. Also, go test
supports providing a package path just like go install
.
What I would propose is that in all relevant commands (build
, flash
, run
, test
), the package to process is determined using the same method. That is: if a command line argument was given that package path is used, and otherwise it is determined based on the current working directory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want to change the behavior of the other commands in this PR, it's out of scope. But I did change test to test if the positional argument is not present and use ".". I think that's what you were asking for, and would work for the other commands.
test.go
Outdated
"strings" | ||
) | ||
|
||
func RunTests() error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file does not seem to be used. I guess this was intended as a test, in which case it should probably be renamed to <something>_test.go
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed it since it wasn't used.
@carolynvs can you rebase this branch on the current dev branch? There are a few conflicts. |
Oh, how much I want this feature! |
OK update to above comment. I need this feature now. How can I help? |
Also, I should note that there is an upcoming big refactor that will conflict with this PR. It would be great to have this merged before then to avoid more (and harder to resolve) merge conflicts. |
* Test command passes flag to compile indicating that a test binary should be built * Adds the test build tag * Imports packages from test files Stuck on how to find the TestMain that I have defined in my test file. It is coming back nil. Not sure how I would reflect on the functions defined in the test files and then build up the test suite (M) in a basic block. First step I figured was calling a simple predefined test suite though.
This is currently failing with panic: interface conversion: ssa.Member is nil, not *ssa.Function
Don't use build/tinygo since it assumes that you are building llvm from source Example: $ go install && make tinygo-test cd tests/tinygotest && tinygo test === RUN TestFail1 --- FAIL: TestFail1 TestFail1 failed because of stuff and things === RUN TestFail2 --- FAIL: TestFail2 TestFail2 failed for reasons === RUN TestPass --- PASS: TestPass exit status 2 FAIL
* Remove getPackageRoot and use . instead * Do not recursively import test files * Remove unused test
I've rebased and addressed the review feedback with the exception of the following items:
|
Hi @carolynvs agreed that this makes some very substantial progress. As you point out, still more is needed on this feature, but can be added in subsequent PRs. Most excellent contribution, thank you so much for putting in all the effort. Now squashing/merging. |
Stuff that works so far:
tinygo test
without passing in a package pathI'm stuck on how to find the TestMain that I have defined in my test file. It is coming back nil when I query for it.
You can try it out by running
make tinygo-test
.After that, I'm not sure how I would reflect on the functions defined in the test files and then build up the test suite (M) in a basic block. First step I figured was calling a simple predefined test suite though. So that is what I'd like to understand before diving into other stuff.
This is for #228