github.com/ubuntu/ubuntu-report@v1.7.4-0.20240410144652-96f37d845fac/pkg/sysmetrics/C/libsysmetrics_test.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"context"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/ubuntu/ubuntu-report/internal/helper"
    18  )
    19  
    20  // The actual test functions are in non-_test.go files
    21  // so that they can use cgo (import "C").
    22  // These wrappers are here for gotest to find.
    23  // Similar technic than in https://golang.org/misc/cgo/test/cgo_test.go
    24  func TestCollect(t *testing.T)                      { testCollect(t) }
    25  func TestSendReport(t *testing.T)                   { testSendReport(t) }
    26  func TestSendDecline(t *testing.T)                  { testSendDecline(t) }
    27  func TestNonInteractiveCollectAndSend(t *testing.T) { testNonInteractiveCollectAndSend(t) }
    28  func TestInteractiveCollectAndSend(t *testing.T)    { testInteractiveCollectAndSend(t) }
    29  
    30  func TestCollectExample(t *testing.T) {
    31  	helper.SkipIfShort(t)
    32  	t.Parallel()
    33  	ensureGCC(t)
    34  
    35  	out, tearDown := helper.TempDir(t)
    36  	defer tearDown()
    37  	lib := buildLib(t, out)
    38  	p := extractExampleFromDoc(t, out, "Collect system info", "", "")
    39  	binary := buildExample(t, out, p, lib)
    40  
    41  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    42  	defer cancel()
    43  	cmd := exec.CommandContext(ctx, binary)
    44  	cmd.Env = append(cmd.Env, "LD_LIBRARY_PATH="+out)
    45  	data, err := cmd.CombinedOutput()
    46  
    47  	if err != nil {
    48  		t.Fatal("we didn't expect an error and got one", err)
    49  	}
    50  
    51  	if !strings.Contains(string(data), expectedReportItem) {
    52  		t.Errorf("we expected at least %s in output, got: '%s", expectedReportItem, string(data))
    53  	}
    54  }
    55  
    56  func TestSendReportExample(t *testing.T) {
    57  	helper.SkipIfShort(t)
    58  	t.Parallel()
    59  	ensureGCC(t)
    60  
    61  	a := helper.Asserter{T: t}
    62  
    63  	serverHit := false
    64  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    65  		serverHit = true
    66  	}))
    67  	defer ts.Close()
    68  
    69  	out, tearDown := helper.TempDir(t)
    70  	defer tearDown()
    71  
    72  	lib := buildLib(t, out)
    73  	p := extractExampleFromDoc(t, out, "Send provided metrics data to server", `""`, `"`+ts.URL+`"`)
    74  	binary := buildExample(t, out, p, lib)
    75  
    76  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    77  	defer cancel()
    78  	cmd := exec.CommandContext(ctx, binary)
    79  	cmd.Env = append(cmd.Env, "LD_LIBRARY_PATH="+out, "XDG_CACHE_HOME="+out)
    80  	err := cmd.Run()
    81  
    82  	if err != nil {
    83  		t.Fatal("we didn't expect an error and got one", err)
    84  	}
    85  
    86  	// There isn't a data race as only the external binary can hit test server,
    87  	// but Go can't know it. To prevent that, shutdown the test server explicitly
    88  	ts.Close()
    89  
    90  	a.Equal(serverHit, true)
    91  	xdgP := filepath.Join(out, "ubuntu-report")
    92  	p = filepath.Join(xdgP, helper.FindInDirectory(t, "", xdgP))
    93  	data, err := ioutil.ReadFile(p)
    94  	if err != nil {
    95  		t.Fatalf("couldn't open report file %s", p)
    96  	}
    97  	d := string(data)
    98  
    99  	if !strings.Contains(d, expectedReportItem) {
   100  		t.Errorf("we expected to find %s in report file, got: %s", expectedReportItem, d)
   101  	}
   102  }
   103  
   104  func TestSendDeclineExample(t *testing.T) {
   105  	helper.SkipIfShort(t)
   106  	t.Parallel()
   107  	ensureGCC(t)
   108  
   109  	a := helper.Asserter{T: t}
   110  
   111  	serverHit := false
   112  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   113  		serverHit = true
   114  	}))
   115  	defer ts.Close()
   116  
   117  	out, tearDown := helper.TempDir(t)
   118  	defer tearDown()
   119  
   120  	lib := buildLib(t, out)
   121  	p := extractExampleFromDoc(t, out, "Send denial message to server", `""`, `"`+ts.URL+`"`)
   122  	binary := buildExample(t, out, p, lib)
   123  
   124  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   125  	defer cancel()
   126  	cmd := exec.CommandContext(ctx, binary)
   127  	cmd.Env = append(cmd.Env, "LD_LIBRARY_PATH="+out, "XDG_CACHE_HOME="+out)
   128  	err := cmd.Run()
   129  
   130  	if err != nil {
   131  		t.Fatal("we didn't expect an error and got one", err)
   132  	}
   133  
   134  	// There isn't a data race as only the external binary can hit test server,
   135  	// but Go can't know it. To prevent that, shutdown the test server explicitly
   136  	ts.Close()
   137  
   138  	a.Equal(serverHit, true)
   139  	xdgP := filepath.Join(out, "ubuntu-report")
   140  	p = filepath.Join(xdgP, helper.FindInDirectory(t, "", xdgP))
   141  	data, err := ioutil.ReadFile(p)
   142  	if err != nil {
   143  		t.Fatalf("couldn't open report file %s", p)
   144  	}
   145  	d := string(data)
   146  
   147  	if !strings.Contains(d, optOutJSON) {
   148  		t.Errorf("we expected to find %s in report file, got: %s", optOutJSON, d)
   149  	}
   150  }
   151  
   152  func TestCollectAndSendExample(t *testing.T) {
   153  	helper.SkipIfShort(t)
   154  	t.Parallel()
   155  	ensureGCC(t)
   156  
   157  	a := helper.Asserter{T: t}
   158  
   159  	serverHit := false
   160  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   161  		serverHit = true
   162  	}))
   163  	defer ts.Close()
   164  
   165  	out, tearDown := helper.TempDir(t)
   166  	defer tearDown()
   167  
   168  	lib := buildLib(t, out)
   169  	p := extractExampleFromDoc(t, out, "Collect and send system info to server", `""`, `"`+ts.URL+`"`)
   170  	binary := buildExample(t, out, p, lib)
   171  
   172  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   173  	defer cancel()
   174  	cmd := exec.CommandContext(ctx, binary)
   175  	cmd.Env = append(cmd.Env, "LD_LIBRARY_PATH="+out, "XDG_CACHE_HOME="+out)
   176  	err := cmd.Run()
   177  
   178  	if err != nil {
   179  		t.Fatal("we didn't expect an error and got one", err)
   180  	}
   181  
   182  	// There isn't a data race as only the external binary can hit test server,
   183  	// but Go can't know it. To prevent that, shutdown the test server explicitly
   184  	ts.Close()
   185  
   186  	a.Equal(serverHit, true)
   187  	xdgP := filepath.Join(out, "ubuntu-report")
   188  	p = filepath.Join(xdgP, helper.FindInDirectory(t, "", xdgP))
   189  	data, err := ioutil.ReadFile(p)
   190  	if err != nil {
   191  		t.Fatalf("couldn't open report file %s", p)
   192  	}
   193  	d := string(data)
   194  
   195  	if !strings.Contains(d, expectedReportItem) {
   196  		t.Errorf("we expected to find %s in report file, got: %s", expectedReportItem, d)
   197  	}
   198  }
   199  
   200  func ensureGCC(t *testing.T) {
   201  	if _, err := exec.LookPath("gcc"); err != nil {
   202  		t.Skip("skipping test: no gcc found:", err)
   203  	}
   204  }
   205  
   206  func buildExample(t *testing.T, dest, example, lib string) string {
   207  	t.Helper()
   208  
   209  	d := filepath.Join(dest, "example")
   210  	cmd := exec.Command("gcc", "-I", dest, "-o", d, example, lib)
   211  	var out bytes.Buffer
   212  	cmd.Stderr = &out
   213  	if err := cmd.Run(); err != nil {
   214  		t.Fatal("couldn't build example binary:", err, "\n", out.String())
   215  	}
   216  	return d
   217  }
   218  
   219  func buildLib(t *testing.T, p string) string {
   220  	t.Helper()
   221  	libName := "libsysmetrics.so.1"
   222  	d := filepath.Join(p, libName)
   223  	cmd := exec.Command("go", "build", "-o", d, "-buildmode=c-shared", "-ldflags", "-extldflags -Wl,-soname,"+libName, "libsysmetrics.go")
   224  	if err := cmd.Run(); err != nil {
   225  		t.Fatal("couldn't build library:", err)
   226  	}
   227  	if err := os.Rename(filepath.Join(p, "libsysmetrics.so.h"), filepath.Join(p, "libsysmetrics.h")); err != nil {
   228  		t.Fatal("couldn't rename header file", err)
   229  	}
   230  	return d
   231  }
   232  
   233  func extractExampleFromDoc(t *testing.T, dir, title, pattern, replace string) string {
   234  	t.Helper()
   235  
   236  	f, err := os.Open("doc.go")
   237  	if err != nil {
   238  		t.Fatal("couldn't open documentation file:", err)
   239  	}
   240  	defer f.Close()
   241  
   242  	p := filepath.Join(dir, strings.Replace(strings.ToLower(title), " ", "_", -1)+".c")
   243  	w, err := os.Create(p)
   244  	if err != nil {
   245  		t.Fatal("couldn't create example file:", err)
   246  	}
   247  	defer w.Close()
   248  
   249  	scanner := bufio.NewScanner(f)
   250  	correctSection := false
   251  	inExample := false
   252  	for scanner.Scan() {
   253  		txt := strings.TrimPrefix(scanner.Text(), "//")
   254  		if strings.HasPrefix(txt, " "+title) {
   255  			correctSection = true
   256  			continue
   257  		}
   258  		if !correctSection {
   259  			continue
   260  		}
   261  		if strings.HasPrefix(txt, " Example") {
   262  			inExample = true
   263  			continue
   264  		}
   265  		if !inExample {
   266  			continue
   267  		}
   268  		// end of example: no space separated content, nor empty line
   269  		if !(strings.HasPrefix(txt, "   ") || txt == "") {
   270  			break
   271  		}
   272  		txt = strings.Replace(strings.TrimPrefix(txt, "   "), pattern, replace, -1)
   273  		if _, err := w.WriteString(txt + "\n"); err != nil {
   274  			t.Fatalf("couldn't write '%s' to destination example file: %v", txt, err)
   275  		}
   276  	}
   277  
   278  	return p
   279  }