github.com/gotranspile/cxgo@v0.3.7/translate_test.go (about) 1 package cxgo 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "math" 9 "math/rand" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "strconv" 14 "sync" 15 "testing" 16 "time" 17 18 "github.com/stretchr/testify/require" 19 ) 20 21 const testDataDir = "./.testdata" 22 23 func failOrSkip(t testing.TB, err error, skip bool) { 24 t.Helper() 25 if err == nil { 26 return 27 } 28 if skip { 29 t.Skip(err) 30 return 31 } 32 require.NoError(t, err) 33 } 34 35 func goBuild(t testing.TB, wd, bin string, files []string, c runConfig) { 36 buf := bytes.NewBuffer(nil) 37 args := []string{"build", "-o", bin} 38 args = append(args, files...) 39 t.Logf("go %q", args) 40 cmd := exec.Command("go", args...) 41 cmd.Dir = wd 42 cmd.Env = os.Environ() 43 if c.Arch32 { 44 cmd.Env = append(cmd.Env, "GOARCH=386") 45 } 46 cmd.Stderr = buf 47 cmd.Stdout = buf 48 err := cmd.Run() 49 if err != nil { 50 err = fmt.Errorf("compilation failed: %w; output:\n%s\n", err, buf) 51 } 52 failOrSkip(t, err, c.Skip) 53 } 54 55 func cmdRun(t testing.TB, wd, bin string, skip bool, args ...string) []byte { 56 buf := bytes.NewBuffer(nil) 57 ebuf := bytes.NewBuffer(nil) 58 cmd := exec.Command(bin, args...) 59 cmd.Dir = wd 60 cmd.Stderr = io.MultiWriter(ebuf, buf) 61 cmd.Stdout = buf 62 timeout := time.NewTimer(time.Minute / 2) 63 defer timeout.Stop() 64 err := cmd.Start() 65 require.NoError(t, err) 66 errc := make(chan error, 1) 67 go func() { 68 errc <- cmd.Wait() 69 }() 70 select { 71 case <-timeout.C: 72 _ = cmd.Process.Kill() 73 require.Fail(t, "timeout") 74 case err = <-errc: 75 if err != nil && skip { 76 t.Skipf("program failed; output:\n%s\n", buf) 77 } 78 require.NoError(t, err, "program failed; output:\n%s\n", buf) 79 } 80 return buf.Bytes() 81 } 82 83 type runConfig struct { 84 Arch32 bool 85 Skip bool 86 BuildOnly bool 87 } 88 89 func goRun(t testing.TB, wd string, files []string, c runConfig, args ...string) []byte { 90 f, err := ioutil.TempFile("", "cxgo_bin_") 91 require.NoError(t, err) 92 _ = f.Close() 93 bin := f.Name() 94 defer os.Remove(bin) 95 goBuild(t, wd, bin, files, c) 96 if c.BuildOnly { 97 return nil 98 } 99 return cmdRun(t, wd, bin, c.Skip, args...) 100 } 101 102 func copyFile(src, dst string) error { 103 s, err := os.Open(src) 104 if err != nil { 105 return err 106 } 107 defer s.Close() 108 109 d, err := os.Create(dst) 110 if err != nil { 111 return err 112 } 113 defer d.Close() 114 115 _, err = io.Copy(d, s) 116 if err != nil { 117 return err 118 } 119 return d.Close() 120 } 121 122 func TestTranslateSnippets(t *testing.T) { 123 runTestTranslate(t, casesTranslateSnippets) 124 } 125 126 var casesTranslateSnippets = []parseCase{ 127 { 128 name: "files", 129 src: ` 130 #include <stdio.h> 131 132 void foo() { 133 char* mode = "r"; 134 FILE* f = fopen("file.dat", mode); 135 if (f == 0) { 136 return; 137 } 138 char b[10]; 139 fread(b, 10, 1, f); 140 fclose(f); 141 } 142 `, 143 exp: ` 144 func foo() { 145 var ( 146 mode *byte = libc.CString("r") 147 f *stdio.File = stdio.FOpen("file.dat", libc.GoString(mode)) 148 ) 149 if f == nil { 150 return 151 } 152 var b [10]byte 153 f.ReadN(&b[0], 10, 1) 154 f.Close() 155 } 156 `, 157 }, 158 } 159 160 const ( 161 kindUint = 1 162 kindInt = 2 163 ) 164 165 type intVal struct { 166 Size int 167 Kind int 168 ValI int64 169 ValU uint64 170 } 171 172 func (v intVal) CType() string { 173 u := "" 174 if v.Kind == kindUint { 175 u = "u" 176 } 177 return fmt.Sprintf("%sint%d_t", u, v.Size*8) 178 } 179 180 func (v intVal) CValue() string { 181 switch v.Kind { 182 case kindUint: 183 return strconv.FormatUint(v.ValU, 10) + "ul" 184 case kindInt: 185 return strconv.FormatInt(v.ValI, 10) + "l" 186 default: 187 panic("should not happen") 188 } 189 } 190 191 type binopTest struct { 192 To intVal 193 X intVal 194 Op BinaryOp 195 Y intVal 196 } 197 198 func (b binopTest) CSrc() string { 199 verb := "d" 200 if b.To.Kind == kindUint { 201 verb = "u" 202 } 203 if b.To.Size == 8 { 204 verb = "ll" + verb 205 } 206 return fmt.Sprintf(` 207 #include <stdint.h> 208 #include <stdio.h> 209 210 int main() { 211 %s a = %s; 212 %s b = %s; 213 %s c = 0; 214 c = a %s b; 215 printf("%%%s\n", c); 216 return 0; 217 } 218 `, 219 b.X.CType(), b.X.CValue(), 220 b.Y.CType(), b.Y.CValue(), 221 b.To.CType(), 222 string(b.Op), 223 verb, 224 ) 225 } 226 227 func randIntVal() intVal { 228 v := intVal{ 229 Kind: kindUint, 230 Size: 1 << (rand.Int() % 4), 231 } 232 switch v.Size { 233 case 1, 2, 4, 8: 234 default: 235 panic(v.Size) 236 } 237 if rand.Int()%2 == 0 { 238 v.Kind = kindInt 239 } 240 switch v.Kind { 241 case kindUint: 242 v.ValU = rand.Uint64() 243 switch v.Size { 244 case 1: 245 v.ValU %= math.MaxUint8 + 1 246 case 2: 247 v.ValU %= math.MaxUint16 + 1 248 case 4: 249 v.ValU %= math.MaxUint32 + 1 250 } 251 case kindInt: 252 v.ValI = rand.Int63() 253 switch v.Size { 254 case 1: 255 v.ValI %= math.MaxInt8 + 1 256 case 2: 257 v.ValI %= math.MaxInt16 + 1 258 case 4: 259 v.ValI %= math.MaxInt32 + 1 260 } 261 if rand.Int()%2 == 0 { 262 v.ValI = -v.ValI 263 } 264 switch v.Size { 265 case 1: 266 for v.ValI > math.MaxInt8 { 267 v.ValI -= math.MaxInt8 268 } 269 for v.ValI < math.MinInt8 { 270 v.ValI += math.MinInt8 271 } 272 case 2: 273 for v.ValI > math.MaxInt16 { 274 v.ValI -= math.MaxInt16 275 } 276 for v.ValI < math.MinInt16 { 277 v.ValI += math.MinInt16 278 } 279 case 4: 280 for v.ValI > math.MaxInt32 { 281 v.ValI -= math.MaxInt32 282 } 283 for v.ValI < math.MinInt32 { 284 v.ValI += math.MinInt32 285 } 286 } 287 } 288 return v 289 } 290 291 func randBinop(op BinaryOp) binopTest { 292 return binopTest{ 293 To: randIntVal(), 294 X: randIntVal(), 295 Op: op, 296 Y: randIntVal(), 297 } 298 } 299 300 func TestImplicitCompat(t *testing.T) { 301 for _, op := range []BinaryOp{ 302 BinOpAdd, 303 BinOpSub, 304 BinOpMult, 305 BinOpDiv, 306 BinOpMod, 307 } { 308 op := op 309 t.Run(string(op), func(t *testing.T) { 310 t.Parallel() 311 for i := 0; i < 25; i++ { 312 func() { 313 b := randBinop(op) 314 for (b.Op == BinOpMod || b.Op == BinOpDiv) && b.Y.ValI == 0 && b.Y.ValU == 0 { 315 b = randBinop(op) 316 } 317 testTranspileOut(t, b.CSrc()) 318 }() 319 } 320 }) 321 } 322 } 323 324 func testTranspileOut(t testing.TB, csrc string) { 325 dir, err := ioutil.TempDir("", "cxgo_cout") 326 require.NoError(t, err) 327 defer os.RemoveAll(dir) 328 329 cdir := filepath.Join(dir, "c") 330 err = os.MkdirAll(cdir, 0755) 331 require.NoError(t, err) 332 333 cfile := filepath.Join(cdir, "main.c") 334 err = ioutil.WriteFile(cfile, []byte(csrc), 0644) 335 require.NoError(t, err) 336 337 // this is required for test to wait other goroutines in case it fails earlier on the main one 338 var wg sync.WaitGroup 339 wg.Add(1) 340 cch := make(chan progOut, 1) 341 go func() { 342 defer wg.Done() 343 cch <- gccCompileAndExec(t, cdir, cfile) 344 }() 345 defer wg.Wait() 346 347 godir := filepath.Join(dir, "golang") 348 err = os.MkdirAll(godir, 0755) 349 require.NoError(t, err) 350 351 goout := goTranspileAndExec(t, ".", godir, cfile) 352 cout := <-cch 353 require.Equal(t, cout.Code, goout.Code, 354 "\nC code: %d (%v)\nGo code: %s (%v)", 355 cout.Code, cout.Err, 356 goout.Code, goout.Err, 357 ) 358 require.Equal(t, cout.Err, goout.Err) 359 t.Logf("// === Output ===\n%s", cout.Out) 360 require.Equal(t, cout.Out, goout.Out, "\n// === C source ===\n%s", csrc) 361 }