Merge "Add errorHints to stdout when read-only file system errors are detected"

This commit is contained in:
Spandan Das
2021-06-30 23:52:19 +00:00
committed by Gerrit Code Review
6 changed files with 150 additions and 2 deletions

View File

@@ -486,6 +486,39 @@ function test_null_build_after_docs {
fi fi
} }
function test_write_to_source_tree {
setup
mkdir -p a
cat > a/Android.bp <<EOF
genrule {
name: "write_to_source_tree",
out: ["write_to_source_tree"],
cmd: "touch file_in_source_tree && touch \$(out)",
}
EOF
readonly EXPECTED_OUT=out/soong/.intermediates/a/write_to_source_tree/gen/write_to_source_tree
readonly ERROR_LOG=${MOCK_TOP}/out/error.log
readonly ERROR_MSG="Read-only file system"
readonly ERROR_HINT_PATTERN="BUILD_BROKEN_SRC_DIR"
# Test in ReadOnly source tree
run_ninja BUILD_BROKEN_SRC_DIR_IS_WRITABLE=false ${EXPECTED_OUT} &> /dev/null && \
fail "Write to source tree should not work in a ReadOnly source tree"
if grep -q "${ERROR_MSG}" ${ERROR_LOG} && grep -q "${ERROR_HINT_PATTERN}" ${ERROR_LOG} ; then
echo Error message and error hint found in logs >/dev/null
else
fail "Did not find Read-only error AND error hint in error.log"
fi
# Test in ReadWrite source tree
run_ninja BUILD_BROKEN_SRC_DIR_IS_WRITABLE=true ${EXPECTED_OUT} &> /dev/null || \
fail "Write to source tree did not succeed in a ReadWrite source tree"
if grep -q "${ERROR_MSG}\|${ERROR_HINT_PATTERN}" ${ERROR_LOG} ; then
fail "Found read-only error OR error hint in error.log"
fi
}
function test_bp2build_smoke { function test_bp2build_smoke {
setup setup
GENERATE_BAZEL_FILES=1 run_soong GENERATE_BAZEL_FILES=1 run_soong
@@ -692,6 +725,7 @@ test_add_file_to_soong_build
test_glob_during_bootstrapping test_glob_during_bootstrapping
test_soong_build_rerun_iff_environment_changes test_soong_build_rerun_iff_environment_changes
test_dump_json_module_graph test_dump_json_module_graph
test_write_to_source_tree
test_bp2build_smoke test_bp2build_smoke
test_bp2build_generates_fake_ninja_file test_bp2build_generates_fake_ninja_file
test_bp2build_null_build test_bp2build_null_build

View File

@@ -126,6 +126,10 @@ run_bp2build() {
GENERATE_BAZEL_FILES=true build/soong/soong_ui.bash --make-mode --skip-ninja --skip-make --skip-soong-tests nothing GENERATE_BAZEL_FILES=true build/soong/soong_ui.bash --make-mode --skip-ninja --skip-make --skip-soong-tests nothing
} }
run_ninja() {
build/soong/soong_ui.bash --make-mode --skip-make --skip-soong-tests "$@"
}
info "Starting Soong integration test suite $(basename $0)" info "Starting Soong integration test suite $(basename $0)"
info "Mock top: $MOCK_TOP" info "Mock top: $MOCK_TOP"

View File

@@ -161,6 +161,10 @@ func NewConfig(ctx Context, args ...string) Config {
ret.distDir = filepath.Join(ret.OutDir(), "dist") ret.distDir = filepath.Join(ret.OutDir(), "dist")
} }
if srcDirIsWritable, ok := ret.environ.Get("BUILD_BROKEN_SRC_DIR_IS_WRITABLE"); ok {
ret.sandboxConfig.SetSrcDirIsRO(srcDirIsWritable == "false")
}
ret.environ.Unset( ret.environ.Unset(
// We're already using it // We're already using it
"USE_SOONG_UI", "USE_SOONG_UI",

View File

@@ -97,8 +97,11 @@ func (c *Cmd) sandboxSupported() bool {
"-u", "nobody", "-u", "nobody",
"-g", sandboxConfig.group, "-g", sandboxConfig.group,
"-R", "/", "-R", "/",
"-B", sandboxConfig.srcDir, // Mount tmp before srcDir
// srcDir is /tmp/.* in integration tests, which is a child dir of /tmp
// nsjail throws an error if a child dir is mounted before its parent
"-B", "/tmp", "-B", "/tmp",
"-B", sandboxConfig.srcDir,
"-B", sandboxConfig.outDir, "-B", sandboxConfig.outDir,
} }

View File

@@ -19,6 +19,8 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"regexp"
"strings"
"syscall" "syscall"
"time" "time"
@@ -158,9 +160,10 @@ func (n *NinjaReader) run() {
err = fmt.Errorf("exited with code: %d", exitCode) err = fmt.Errorf("exited with code: %d", exitCode)
} }
outputWithErrorHint := errorHintGenerator.GetOutputWithErrorHint(msg.EdgeFinished.GetOutput(), exitCode)
n.status.FinishAction(ActionResult{ n.status.FinishAction(ActionResult{
Action: started, Action: started,
Output: msg.EdgeFinished.GetOutput(), Output: outputWithErrorHint,
Error: err, Error: err,
Stats: ActionResultStats{ Stats: ActionResultStats{
UserTime: msg.EdgeFinished.GetUserTime(), UserTime: msg.EdgeFinished.GetUserTime(),
@@ -219,3 +222,53 @@ func readVarInt(r *bufio.Reader) (int, error) {
return ret, nil return ret, nil
} }
// key is pattern in stdout/stderr
// value is error hint
var allErrorHints = map[string]string{
"Read-only file system": `\nWrite to a read-only file system detected. Possible fixes include
1. Generate file directly to out/ which is ReadWrite, #recommend solution
2. BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST := <my/path/1> <my/path/2> #discouraged, subset of source tree will be RW
3. BUILD_BROKEN_SRC_DIR_IS_WRITABLE := true #highly discouraged, entire source tree will be RW
`,
}
var errorHintGenerator = *newErrorHintGenerator(allErrorHints)
type ErrorHintGenerator struct {
allErrorHints map[string]string
allErrorHintPatternsCompiled *regexp.Regexp
}
func newErrorHintGenerator(allErrorHints map[string]string) *ErrorHintGenerator {
var allErrorHintPatterns []string
for errorHintPattern, _ := range allErrorHints {
allErrorHintPatterns = append(allErrorHintPatterns, errorHintPattern)
}
allErrorHintPatternsRegex := strings.Join(allErrorHintPatterns[:], "|")
re := regexp.MustCompile(allErrorHintPatternsRegex)
return &ErrorHintGenerator{
allErrorHints: allErrorHints,
allErrorHintPatternsCompiled: re,
}
}
func (errorHintGenerator *ErrorHintGenerator) GetOutputWithErrorHint(rawOutput string, buildExitCode int) string {
if buildExitCode == 0 {
return rawOutput
}
errorHint := errorHintGenerator.getErrorHint(rawOutput)
if errorHint == nil {
return rawOutput
}
return rawOutput + *errorHint
}
// Returns the error hint corresponding to the FIRST match in raw output
func (errorHintGenerator *ErrorHintGenerator) getErrorHint(rawOutput string) *string {
firstMatch := errorHintGenerator.allErrorHintPatternsCompiled.FindString(rawOutput)
if _, found := errorHintGenerator.allErrorHints[firstMatch]; found {
errorHint := errorHintGenerator.allErrorHints[firstMatch]
return &errorHint
}
return nil
}

View File

@@ -43,3 +43,53 @@ func TestNinjaReader_Close(t *testing.T) {
t.Errorf("nr.Close timed out, %s > %s", g, w) t.Errorf("nr.Close timed out, %s > %s", g, w)
} }
} }
// Test that error hint is added to output if available
func TestNinjaReader_CorrectErrorHint(t *testing.T) {
errorPattern1 := "pattern-1 in input"
errorHint1 := "\n Fix by doing task 1"
errorPattern2 := "pattern-2 in input"
errorHint2 := "\n Fix by doing task 2"
mockErrorHints := make(map[string]string)
mockErrorHints[errorPattern1] = errorHint1
mockErrorHints[errorPattern2] = errorHint2
errorHintGenerator := *newErrorHintGenerator(mockErrorHints)
testCases := []struct {
rawOutput string
buildExitCode int
expectedFinalOutput string
testCaseErrorMessage string
}{
{
rawOutput: "ninja build was successful",
buildExitCode: 0,
expectedFinalOutput: "ninja build was successful",
testCaseErrorMessage: "raw output changed when build was successful",
},
{
rawOutput: "ninja build failed",
buildExitCode: 1,
expectedFinalOutput: "ninja build failed",
testCaseErrorMessage: "raw output changed even when no error hint pattern was found",
},
{
rawOutput: "ninja build failed: " + errorPattern1 + "some footnotes",
buildExitCode: 1,
expectedFinalOutput: "ninja build failed: " + errorPattern1 + "some footnotes" + errorHint1,
testCaseErrorMessage: "error hint not added despite pattern match",
},
{
rawOutput: "ninja build failed: " + errorPattern2 + errorPattern1,
buildExitCode: 1,
expectedFinalOutput: "ninja build failed: " + errorPattern2 + errorPattern1 + errorHint2,
testCaseErrorMessage: "error hint should be added for first pattern match in raw output",
},
}
for _, testCase := range testCases {
actualFinalOutput := errorHintGenerator.GetOutputWithErrorHint(testCase.rawOutput, testCase.buildExitCode)
if actualFinalOutput != testCase.expectedFinalOutput {
t.Errorf(testCase.testCaseErrorMessage+"\nexpected: %s\ngot: %s", testCase.expectedFinalOutput, actualFinalOutput)
}
}
}