1 # Regression test for https://go.dev/issue/64423:
2 #
3 # When we parse the version for a Clang binary, we should accept
4 # an arbitrary vendor prefix, which (as of 2023) may be injected
5 # by defining CLANG_VENDOR when building clang itself.
6 #
7 # Since we don't want to actually rebuild the Clang toolchain in
8 # this test, we instead simulate it by injecting a fake "clang"
9 # binary that runs the real one as a subprocess.
10
11 [!cgo] skip
12 [short] skip 'builds and links a fake clang binary'
13 [!cc:clang] skip 'test is specific to clang version parsing'
14
15 # Save the location of the real clang command for our fake one to use.
16 go run ./which clang
17 cp stdout $WORK/.realclang
18
19 # Build a fake clang and ensure that it is the one in $PATH.
20 mkdir $WORK/bin
21 go build -o $WORK/bin/clang$GOEXE ./fakeclang
22 [!GOOS:plan9] env PATH=$WORK${/}bin
23 [GOOS:plan9] env path=$WORK${/}bin
24
25 # Force CGO_ENABLED=1 so that the following commands should error
26 # out if the fake clang doesn't work.
27 env CGO_ENABLED=1
28
29 # The bug in https://go.dev/issue/64423 resulted in cache keys that
30 # didn't contain any information about the C compiler.
31 # Since the bug was in cache key computation, isolate the cache:
32 # if we change the way caching works, we want the test to fail
33 # instead of accidentally reusing the cached information from a
34 # previous test run.
35 env GOCACHE=$WORK${/}.cache
36 mkdir $GOCACHE
37
38 go build -x runtime/cgo
39
40 # Tell our fake clang to stop working.
41 # Previously, 'go build -x runtime/cgo' would continue to
42 # succeed because both the broken clang and the non-broken one
43 # resulted in a cache key with no clang version information.
44 env GO_BREAK_CLANG=1
45 ! go build -x runtime/cgo
46 stderr '# runtime/cgo\nGO_BREAK_CLANG is set'
47
48 -- go.mod --
49 module example/issue64423
50 go 1.20
51 -- which/main.go --
52 package main
53
54 import (
55 "os"
56 "os/exec"
57 )
58
59 func main() {
60 path, err := exec.LookPath(os.Args[1])
61 if err != nil {
62 panic(err)
63 }
64 os.Stdout.WriteString(path)
65 }
66 -- fakeclang/main.go --
67 package main
68
69 import (
70 "bufio"
71 "bytes"
72 "log"
73 "os"
74 "os/exec"
75 "path/filepath"
76 "strings"
77 )
78
79 func main() {
80 if os.Getenv("GO_BREAK_CLANG") != "" {
81 os.Stderr.WriteString("GO_BREAK_CLANG is set\n")
82 os.Exit(1)
83 }
84
85 b, err := os.ReadFile(filepath.Join(os.Getenv("WORK"), ".realclang"))
86 if err != nil {
87 log.Fatal(err)
88 }
89 clang := string(bytes.TrimSpace(b))
90 cmd := exec.Command(clang, os.Args[1:]...)
91 cmd.Stdout = os.Stdout
92 stderr, err := cmd.StderrPipe()
93 if err != nil {
94 log.Fatal(err)
95 }
96
97 if err := cmd.Start(); err != nil {
98 log.Fatal(err)
99 }
100
101 r := bufio.NewReader(stderr)
102 for {
103 line, err := r.ReadString('\n')
104 if line != "" {
105 if strings.Contains(line, "clang version") {
106 // Simulate a clang version string with an arbitrary vendor prefix.
107 const vendorString = "Gopher Solutions Unlimited "
108 os.Stderr.WriteString(vendorString)
109 }
110 os.Stderr.WriteString(line)
111 }
112 if err != nil {
113 break
114 }
115 }
116 os.Stderr.Close()
117
118 if err := cmd.Wait(); err != nil {
119 os.Exit(1)
120 }
121 }
122
View as plain text