Unverified Commit eece582e authored by Cristian Maglie's avatar Cristian Maglie Committed by GitHub

Fixed compiler output capture in JSON mode (#2078)

* Added integration test for #1698

* Added helper functions to handle concurrent writes to ctx.Stdout/Stderr

* Do not alter ctx.Stderr/Stdout in ExecCommand

Some instances of ctx.Stdout may now contains nil since it's no more
altered during ExecCommand.

* Handle multi-threaded compile streams
parent 6992de7a
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
"github.com/arduino/go-paths-helper" "github.com/arduino/go-paths-helper"
"github.com/arduino/go-properties-orderedmap" "github.com/arduino/go-properties-orderedmap"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.bug.st/testifyjson/requirejson"
) )
func TestRuntimeToolPropertiesGeneration(t *testing.T) { func TestRuntimeToolPropertiesGeneration(t *testing.T) {
...@@ -96,3 +97,22 @@ func TestCompileBuildPathInsideSketch(t *testing.T) { ...@@ -96,3 +97,22 @@ func TestCompileBuildPathInsideSketch(t *testing.T) {
_, _, err = cli.Run("compile", "-b", "arduino:avr:mega", "--build-path", "build-mega") _, _, err = cli.Run("compile", "-b", "arduino:avr:mega", "--build-path", "build-mega")
require.NoError(t, err) require.NoError(t, err)
} }
func TestCompilerErrOutput(t *testing.T) {
env, cli := integrationtest.CreateArduinoCLIWithEnvironment(t)
defer env.CleanUp()
// Run update-index with our test index
_, _, err := cli.Run("core", "install", "arduino:avr@1.8.5")
require.NoError(t, err)
// prepare sketch
sketch, err := paths.New("testdata", "blink_with_wrong_cpp").Abs()
require.NoError(t, err)
// Run compile and catch err stream
out, _, err := cli.Run("compile", "-b", "arduino:avr:uno", "--format", "json", sketch.String())
require.Error(t, err)
compilerErr := requirejson.Parse(t, out).Query(".compiler_err")
compilerErr.MustContain(`"error"`)
}
...@@ -152,7 +152,7 @@ func (s *Preprocess) Run(ctx *types.Context) error { ...@@ -152,7 +152,7 @@ func (s *Preprocess) Run(ctx *types.Context) error {
} }
// Output arduino-preprocessed source // Output arduino-preprocessed source
ctx.Stdout.Write([]byte(ctx.Source)) ctx.WriteStdout([]byte(ctx.Source))
return nil return nil
} }
......
...@@ -190,7 +190,15 @@ func compileFileWithRecipe(ctx *types.Context, sourcePath *paths.Path, source *p ...@@ -190,7 +190,15 @@ func compileFileWithRecipe(ctx *types.Context, sourcePath *paths.Path, source *p
ctx.CompilationDatabase.Add(source, command) ctx.CompilationDatabase.Add(source, command)
} }
if !objIsUpToDate && !ctx.OnlyUpdateCompilationDatabase { if !objIsUpToDate && !ctx.OnlyUpdateCompilationDatabase {
_, _, err = utils.ExecCommand(ctx, command, utils.ShowIfVerbose /* stdout */, utils.Show /* stderr */) // Since this compile could be multithreaded, we first capture the command output
stdout, stderr, err := utils.ExecCommand(ctx, command, utils.Capture, utils.Capture)
// and transfer all at once at the end...
if ctx.Verbose {
ctx.WriteStdout(stdout)
}
ctx.WriteStderr(stderr)
// ...and then return the error
if err != nil { if err != nil {
return nil, errors.WithStack(err) return nil, errors.WithStack(err)
} }
......
...@@ -408,7 +408,7 @@ func findIncludesUntilDone(ctx *types.Context, cache *includeCache, sourceFile t ...@@ -408,7 +408,7 @@ func findIncludesUntilDone(ctx *types.Context, cache *includeCache, sourceFile t
return errors.New(tr("Internal error in cache")) return errors.New(tr("Internal error in cache"))
} }
} }
ctx.Stderr.Write(preproc_stderr) ctx.WriteStderr(preproc_stderr)
return errors.WithStack(preproc_err) return errors.WithStack(preproc_err)
} }
......
...@@ -146,6 +146,6 @@ func (s *OutputCodeCompletions) Run(ctx *types.Context) error { ...@@ -146,6 +146,6 @@ func (s *OutputCodeCompletions) Run(ctx *types.Context) error {
// we assume it is a json, let's make it compliant at least // we assume it is a json, let's make it compliant at least
ctx.CodeCompletions = "[]" ctx.CodeCompletions = "[]"
} }
fmt.Fprintln(ctx.Stdout, ctx.CodeCompletions) ctx.WriteStdout([]byte(ctx.CodeCompletions))
return nil return nil
} }
...@@ -249,3 +249,21 @@ func (ctx *Context) Warn(msg string) { ...@@ -249,3 +249,21 @@ func (ctx *Context) Warn(msg string) {
} }
ctx.stdLock.Unlock() ctx.stdLock.Unlock()
} }
func (ctx *Context) WriteStdout(data []byte) (int, error) {
ctx.stdLock.Lock()
defer ctx.stdLock.Unlock()
if ctx.Stdout == nil {
return os.Stdout.Write(data)
}
return ctx.Stdout.Write(data)
}
func (ctx *Context) WriteStderr(data []byte) (int, error) {
ctx.stdLock.Lock()
defer ctx.stdLock.Unlock()
if ctx.Stderr == nil {
return os.Stderr.Write(data)
}
return ctx.Stderr.Write(data)
}
...@@ -184,13 +184,6 @@ const ( ...@@ -184,13 +184,6 @@ const (
) )
func ExecCommand(ctx *types.Context, command *exec.Cmd, stdout int, stderr int) ([]byte, []byte, error) { func ExecCommand(ctx *types.Context, command *exec.Cmd, stdout int, stderr int) ([]byte, []byte, error) {
if ctx.Stdout == nil {
ctx.Stdout = os.Stdout
}
if ctx.Stderr == nil {
ctx.Stderr = os.Stderr
}
if ctx.Verbose { if ctx.Verbose {
ctx.Info(PrintableCommand(command.Args)) ctx.Info(PrintableCommand(command.Args))
} }
...@@ -198,15 +191,23 @@ func ExecCommand(ctx *types.Context, command *exec.Cmd, stdout int, stderr int) ...@@ -198,15 +191,23 @@ func ExecCommand(ctx *types.Context, command *exec.Cmd, stdout int, stderr int)
if stdout == Capture { if stdout == Capture {
buffer := &bytes.Buffer{} buffer := &bytes.Buffer{}
command.Stdout = buffer command.Stdout = buffer
} else if stdout == Show || stdout == ShowIfVerbose && ctx.Verbose { } else if stdout == Show || (stdout == ShowIfVerbose && ctx.Verbose) {
if ctx.Stdout != nil {
command.Stdout = ctx.Stdout command.Stdout = ctx.Stdout
} else {
command.Stdout = os.Stdout
}
} }
if stderr == Capture { if stderr == Capture {
buffer := &bytes.Buffer{} buffer := &bytes.Buffer{}
command.Stderr = buffer command.Stderr = buffer
} else if stderr == Show || stderr == ShowIfVerbose && ctx.Verbose { } else if stderr == Show || (stderr == ShowIfVerbose && ctx.Verbose) {
if ctx.Stderr != nil {
command.Stderr = ctx.Stderr command.Stderr = ctx.Stderr
} else {
command.Stderr = os.Stderr
}
} }
err := command.Start() err := command.Start()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment