diff --git a/README.md b/README.md index d28db110..56593341 100644 --- a/README.md +++ b/README.md @@ -176,8 +176,8 @@ Here are some examples related to Go syntax: * [concat](_demo/concat/concat.go): define a variadic function * [genints](_demo/genints/genints.go): various forms of closure usage (including C function, recv.method and anonymous function) -* [errors](_demo/errors/errors.go): demo to implement error interface -* [defer](_demo/defer/defer.go): defer demo +* [errors](_cmptest/errors/errors.go): demo to implement error interface +* [defer](_cmptest/defer/defer.go): defer demo * [goroutine](_demo/goroutine/goroutine.go): goroutine demo diff --git a/_demo/defer/defer.go b/_cmptest/defer/defer.go similarity index 100% rename from _demo/defer/defer.go rename to _cmptest/defer/defer.go diff --git a/_demo/errors/errors.go b/_cmptest/errors/errors.go similarity index 96% rename from _demo/errors/errors.go rename to _cmptest/errors/errors.go index 4e73a70c..4c4080d5 100644 --- a/_demo/errors/errors.go +++ b/_cmptest/errors/errors.go @@ -17,6 +17,5 @@ func (e *errorString) Error() string { func main() { err := New("an error") - println(err) println(err.Error()) } diff --git a/_demo/genints/gen_ints.go b/_demo/genints/genints.go similarity index 100% rename from _demo/genints/gen_ints.go rename to _demo/genints/genints.go diff --git a/cmd/internal/run/run.go b/cmd/internal/run/run.go index 8c0a9fe1..4d314cde 100644 --- a/cmd/internal/run/run.go +++ b/cmd/internal/run/run.go @@ -35,14 +35,29 @@ var Cmd = &base.Command{ Short: "Compile and run Go program", } +// llgo cmptest +var CmpTestCmd = &base.Command{ + UsageLine: "llgo cmptest [build flags] package [arguments...]", + Short: "Compile programs by llgo and go, run them and do comparative tests (stdout/stderr/exitcode)", +} + func init() { Cmd.Run = runCmd + CmpTestCmd.Run = runCmpTest } func runCmd(cmd *base.Command, args []string) { + runCmdEx(cmd, args, build.ModeRun) +} + +func runCmpTest(cmd *base.Command, args []string) { + runCmdEx(cmd, args, build.ModeCmpTest) +} + +func runCmdEx(cmd *base.Command, args []string, mode build.Mode) { args, runArgs, err := parseRunArgs(args) check(err) - conf := build.NewDefaultConf(build.ModeRun) + conf := build.NewDefaultConf(mode) conf.RunArgs = runArgs build.Do(args, conf) } diff --git a/cmd/llgo/llgo.go b/cmd/llgo/llgo.go index 85304dd1..69c4444d 100644 --- a/cmd/llgo/llgo.go +++ b/cmd/llgo/llgo.go @@ -43,6 +43,7 @@ func init() { build.Cmd, install.Cmd, run.Cmd, + run.CmpTestCmd, clean.Cmd, } } diff --git a/internal/build/build.go b/internal/build/build.go index 7fdc440e..de0b52c0 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -50,6 +50,7 @@ const ( ModeBuild Mode = iota ModeInstall ModeRun + ModeCmpTest ) const ( @@ -380,7 +381,8 @@ func linkMainPkg(pkg *packages.Package, pkgs []*aPackage, llFiles []string, conf err := clang.New("").Exec(args...) check(err) - if mode == ModeRun { + switch mode { + case ModeRun: cmd := exec.Command(app, conf.RunArgs...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout @@ -389,6 +391,8 @@ func linkMainPkg(pkg *packages.Package, pkgs []*aPackage, llFiles []string, conf if s := cmd.ProcessState; s != nil { os.Exit(s.ExitCode()) } + case ModeCmpTest: + cmpTest("", pkgPath, app, conf.RunArgs) } return } @@ -687,38 +691,6 @@ func isPkgInMod(pkgPath, modPath string) bool { return false } -/* -func llgoPkgLinkFile(pkgPath string) string { - // if kind == cl.PkgLinkBitCode { - // return filepath.Join(llgoRoot()+pkgPath[len(llgoModPath):], "llgo_autogen.bc") - // } - llFile := filepath.Join(llgoRoot()+pkgPath[len(llgoModPath):], "llgo_autogen.ll") - if _, err := os.Stat(llFile); os.IsNotExist(err) { - decodeLinkFile(llFile) - } - return llFile -} - -// *.ll => *.lla -func decodeLinkFile(llFile string) { - zipFile := llFile + "a" - zipf, err := zip.OpenReader(zipFile) - if err != nil { - return - } - defer zipf.Close() - f, err := zipf.Open("llgo_autogen.ll") - if err != nil { - return - } - defer f.Close() - data, err := io.ReadAll(f) - if err == nil { - os.WriteFile(llFile, data, 0644) - } -} -*/ - func decodeFile(outFile string, zipf *zip.File) (err error) { f, err := zipf.Open() if err != nil { diff --git a/internal/build/cmptest.go b/internal/build/cmptest.go new file mode 100644 index 00000000..d7b8a521 --- /dev/null +++ b/internal/build/cmptest.go @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package build + +import ( + "bytes" + "errors" + "fmt" + "io" + "log" + "os" + "os/exec" +) + +func cmpTest(dir, pkgPath, llApp string, runArgs []string) { + var goOut, goErr bytes.Buffer + var llgoOut, llgoErr bytes.Buffer + var llgoRunErr = runApp(runArgs, dir, &llgoOut, &llgoErr, llApp) + var goRunErr = runApp(runArgs, dir, &goOut, &goErr, "go", "run", pkgPath) + checkEqual("output", llgoOut.Bytes(), goOut.Bytes()) + checkEqual("stderr", llgoErr.Bytes(), goErr.Bytes()) + checkEqualRunErr(llgoRunErr, goRunErr) +} + +func checkEqualRunErr(llgoRunErr, goRunErr error) { + if llgoRunErr == goRunErr { + return + } + fmt.Fprintln(os.Stderr, "=> Exit:", llgoRunErr) + fmt.Fprintln(os.Stderr, "\n=> Expected Exit:", goRunErr) +} + +func checkEqual(prompt string, a, expected []byte) { + if bytes.Equal(a, expected) { + return + } + + fmt.Fprintln(os.Stderr, "=> Result of", prompt) + os.Stderr.Write(a) + + fmt.Fprintln(os.Stderr, "\n=> Expected", prompt) + os.Stderr.Write(expected) + + fatal(errors.New("checkEqual: unexpected " + prompt)) +} + +func runApp(runArgs []string, dir string, stdout, stderr io.Writer, app string, args ...string) error { + if len(runArgs) > 0 { + if len(args) > 0 { + args = append(args, runArgs...) + } else { + args = runArgs + } + } + cmd := exec.Command(app, args...) + cmd.Dir = dir + cmd.Stdout = stdout + cmd.Stderr = stderr + return cmd.Run() +} + +func fatal(err error) { + log.Panicln(err) +} diff --git a/internal/runtime/z_print.go b/internal/runtime/z_print.go index 6c426edc..85c09b70 100644 --- a/internal/runtime/z_print.go +++ b/internal/runtime/z_print.go @@ -22,16 +22,19 @@ import ( "github.com/goplus/llgo/c" ) -func PrintByte(v byte) { - c.Fputc(c.Int(v), c.Stderr) +func boolCStr(v bool) *c.Char { + if v { + return c.Str("true") + } + return c.Str("false") } func PrintBool(v bool) { - if v { - c.Fprintf(c.Stderr, c.Str("true")) - } else { - c.Fprintf(c.Stderr, c.Str("false")) - } + c.Fprintf(c.Stderr, boolCStr(v)) +} + +func PrintByte(v byte) { + c.Fputc(c.Int(v), c.Stderr) } func PrintFloat(v float64) {