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  }