github.com/nathants/docker-trace@v0.0.0-20220831131939-668bc05a257b/lib/minify_test.go (about)

     1  package lib
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"crypto/md5"
     7  	"crypto/tls"
     8  	"encoding/hex"
     9  	"fmt"
    10  	"io"
    11  	"net/http"
    12  	"os"
    13  	"os/exec"
    14  	"path"
    15  	"runtime"
    16  	"strings"
    17  	"syscall"
    18  	"testing"
    19  	"time"
    20  )
    21  
    22  func md5SumMinify(bytes []byte) string {
    23  	hash := md5.Sum(bytes)
    24  	return hex.EncodeToString(hash[:])
    25  }
    26  
    27  func runStdinMinify(stdin string, command ...string) error {
    28  	cmd := exec.Command(command[0], command[1:]...)
    29  	cmd.Stderr = os.Stderr
    30  	cmd.Stdout = os.Stdout
    31  	cmd.Stdin = bytes.NewBufferString(stdin + "\n")
    32  	return cmd.Run()
    33  }
    34  
    35  func runStdoutMinify(command ...string) (string, error) {
    36  	cmd := exec.Command(command[0], command[1:]...)
    37  	var stdout bytes.Buffer
    38  	cmd.Stderr = os.Stderr
    39  	cmd.Stdout = &stdout
    40  	err := cmd.Run()
    41  	return strings.Trim(stdout.String(), "\n"), err
    42  }
    43  
    44  func runStdoutStderrChanMinify(command ...string) (<-chan string, <-chan string, func(), error) {
    45  	cmd := exec.Command(command[0], command[1:]...)
    46  	stderr, err := cmd.StderrPipe()
    47  	if err != nil {
    48  		return nil, nil, nil, err
    49  	}
    50  	stdout, err := cmd.StdoutPipe()
    51  	if err != nil {
    52  		return nil, nil, nil, err
    53  	}
    54  	stderrChan := make(chan string)
    55  	stdoutChan := make(chan string, 1024*1024)
    56  	tail := func(c chan<- string, r io.ReadCloser) {
    57  		// defer func() {}()
    58  		buf := bufio.NewReader(r)
    59  		for {
    60  			line, err := buf.ReadBytes('\n')
    61  			if err != nil {
    62  				close(c)
    63  				return
    64  			}
    65  			c <- strings.TrimRight(string(line), "\n")
    66  		}
    67  	}
    68  	go tail(stderrChan, stderr)
    69  	go tail(stdoutChan, stdout)
    70  	err = cmd.Start()
    71  	if err != nil {
    72  		return nil, nil, nil, err
    73  	}
    74  	go func() {
    75  		// defer func() {}()
    76  		_ = cmd.Wait()
    77  	}()
    78  	cancel := func() {
    79  		_ = syscall.Kill(cmd.Process.Pid, syscall.SIGINT)
    80  	}
    81  	return stdoutChan, stderrChan, cancel, err
    82  }
    83  
    84  func runMinify(command ...string) error {
    85  	cmd := exec.Command(command[0], command[1:]...)
    86  	cmd.Stderr = os.Stderr
    87  	cmd.Stdout = os.Stdout
    88  	return cmd.Run()
    89  }
    90  
    91  func runQuietMinify(command ...string) error {
    92  	cmd := exec.Command(command[0], command[1:]...)
    93  	return cmd.Run()
    94  }
    95  
    96  func climbGitRootMinify() {
    97  	_, filename, _, _ := runtime.Caller(1)
    98  	err := os.Chdir(path.Dir(filename))
    99  	if err != nil {
   100  		panic(err)
   101  	}
   102  outer:
   103  	for {
   104  		files, err := os.ReadDir(".")
   105  		if err != nil {
   106  			panic(err)
   107  		}
   108  		for _, file := range files {
   109  			if file.IsDir() && file.Name() == ".git" {
   110  				break outer
   111  			}
   112  			if file.Name() == "/" {
   113  				panic("/")
   114  			}
   115  		}
   116  		err = os.Chdir("..")
   117  		if err != nil {
   118  			panic(err)
   119  		}
   120  	}
   121  }
   122  
   123  func ensureDockerTraceMinify() {
   124  	err := runMinify("go", "build", ".")
   125  	if err != nil {
   126  		panic(err)
   127  	}
   128  }
   129  
   130  func ensureTestContainerMinify(app, kind string) string {
   131  	stdout, err := runStdoutMinify("bash", "-c", fmt.Sprintf("cat examples/%s/*", app))
   132  	if err != nil {
   133  		panic(err)
   134  	}
   135  	hash := md5SumMinify([]byte(stdout))
   136  	container := fmt.Sprintf("docker-trace:minify-%s-%s-%s", app, kind, hash)
   137  	if runQuietMinify("docker", "inspect", container) != nil {
   138  		err = runMinify("docker", "build", "-t", container, "--network", "host", "-f", "examples/"+app+"/Dockerfile."+kind, "examples/"+app)
   139  		if err != nil {
   140  			panic(err)
   141  		}
   142  	}
   143  	return container
   144  }
   145  
   146  func ensureSetupMinify(app, kind string) string {
   147  	_ = runQuietMinify("bash", "-c", "docker kill $(docker ps -q)")
   148  	climbGitRootMinify()
   149  	ensureDockerTraceMinify()
   150  	container := ensureTestContainerMinify(app, kind)
   151  	return container
   152  }
   153  
   154  func testWeb(t *testing.T, app, kind string) {
   155  	container := ensureSetupMinify(app, kind)
   156  	fmt.Println("start trace container")
   157  	stdoutChan, stderrChan, cancel, err := runStdoutStderrChanMinify("./docker-trace", "files")
   158  	if err != nil {
   159  		t.Error(err)
   160  		return
   161  	}
   162  	fmt.Println("wait for trace container ready")
   163  	line := <-stderrChan
   164  	if line != "ready" {
   165  		t.Error(line)
   166  		return
   167  	}
   168  	fmt.Println("trace container ready, start test container")
   169  	id, err := runStdoutMinify("docker", "run", "-d", "-t", "--rm", "--network", "host", container)
   170  	if err != nil {
   171  		t.Error(err)
   172  		return
   173  	}
   174  	tr := &http.Transport{
   175  		TLSClientConfig:     &tls.Config{InsecureSkipVerify: true},
   176  		IdleConnTimeout:     1 * time.Second,
   177  		TLSHandshakeTimeout: 1 * time.Second,
   178  	}
   179  	client := &http.Client{
   180  		Transport: tr,
   181  		Timeout:   1 * time.Second,
   182  	}
   183  	//
   184  	start := time.Now()
   185  	for {
   186  		if time.Since(start) > 10*time.Second {
   187  			t.Error("timeout")
   188  			return
   189  		}
   190  		out, err := client.Get("https://localhost:8080/hello/xyz")
   191  		if err == nil {
   192  			bytes, _ := io.ReadAll(out.Body)
   193  			_ = out.Body.Close()
   194  			fmt.Println(out.StatusCode, string(bytes))
   195  			break
   196  		}
   197  		fmt.Println(err)
   198  		time.Sleep(1 * time.Second)
   199  	}
   200  	fmt.Println("test passed, kill test container")
   201  	//
   202  	err = runMinify("docker", "kill", id)
   203  	if err != nil {
   204  		t.Error(err)
   205  		return
   206  	}
   207  	//
   208  	fmt.Println("cancel trace container and drain output")
   209  	cancel()
   210  	var files []string
   211  	for line := range stdoutChan {
   212  		parts := strings.SplitN(line, " ", 2)
   213  		if len(parts) != 2 {
   214  			fmt.Println("skipping bad line:", line)
   215  			continue
   216  		}
   217  		fileID := parts[0]
   218  		file := parts[1]
   219  		if id == fileID {
   220  			files = append(files, file)
   221  		}
   222  	}
   223  	//
   224  	fmt.Println("check that test container is not reachable")
   225  	out, err := client.Get("https://localhost:8080/hello/xyz")
   226  	if err == nil {
   227  		_ = out.Body.Close()
   228  		t.Error("server should have been killed")
   229  		return
   230  	}
   231  	//
   232  	fmt.Println("start minification")
   233  	err = runStdinMinify(strings.Join(files, "\n"), "./docker-trace", "minify", container, container+"-min")
   234  	if err != nil {
   235  		t.Error(err)
   236  		return
   237  	}
   238  	//
   239  	fmt.Println("start minified container")
   240  	id, err = runStdoutMinify("docker", "run", "-d", "--network", "host", "--rm", container+"-min")
   241  	if err != nil {
   242  		t.Error(err)
   243  		return
   244  	}
   245  	//
   246  	start = time.Now()
   247  	for {
   248  		if time.Since(start) > 5*time.Second {
   249  			t.Error("timeout")
   250  			return
   251  		}
   252  		out, err := client.Get("https://localhost:8080/hello/xyz")
   253  		if err == nil {
   254  			bytes, _ := io.ReadAll(out.Body)
   255  			_ = out.Body.Close()
   256  			fmt.Println(out.StatusCode, string(bytes))
   257  			break
   258  		}
   259  		fmt.Println(err)
   260  		time.Sleep(1 * time.Second)
   261  	}
   262  	fmt.Println("compare image sizes")
   263  	//
   264  	stdout, err := runStdoutMinify("docker", "inspect", container, "-f", "{{.Size}}")
   265  	if err != nil {
   266  		t.Error(err)
   267  		return
   268  	}
   269  	size := Atoi(stdout)
   270  	//
   271  	stdout, err = runStdoutMinify("docker", "inspect", container+"-min", "-f", "{{.Size}}")
   272  	if err != nil {
   273  		t.Error(err)
   274  		return
   275  	}
   276  	sizeMin := Atoi(stdout)
   277  	if !(sizeMin < size) {
   278  		t.Error("not smaller")
   279  		return
   280  	}
   281  	err = runMinify("docker", "kill", id)
   282  	if err != nil {
   283  		t.Error(err)
   284  		return
   285  	}
   286  }
   287  
   288  func TestGoWebArch(t *testing.T) {
   289  	testWeb(t, "go-web", "arch")
   290  }
   291  
   292  func TestGoWebAlpine(t *testing.T) {
   293  	testWeb(t, "go-web", "alpine")
   294  }
   295  
   296  func TestGoWebDebian(t *testing.T) {
   297  	testWeb(t, "go-web", "debian")
   298  }
   299  
   300  func TestGoWebUbuntu(t *testing.T) {
   301  	testWeb(t, "go-web", "ubuntu")
   302  }
   303  
   304  func TestGoWebAmzn(t *testing.T) {
   305  	testWeb(t, "go-web", "amzn")
   306  }
   307  
   308  func TestPythonWebArch(t *testing.T) {
   309  	testWeb(t, "python3-web", "arch")
   310  }
   311  
   312  func TestPythonWebAlpine(t *testing.T) {
   313  	testWeb(t, "python3-web", "alpine")
   314  }
   315  
   316  func TestPythonWebDebian(t *testing.T) {
   317  	testWeb(t, "python3-web", "debian")
   318  }
   319  
   320  func TestPythonWebUbuntu(t *testing.T) {
   321  	testWeb(t, "python3-web", "ubuntu")
   322  }
   323  
   324  func TestPythonWebAmzn(t *testing.T) {
   325  	testWeb(t, "python3-web", "amzn")
   326  }
   327  
   328  func TestNodeWebArch(t *testing.T) {
   329  	testWeb(t, "node-web", "arch")
   330  }
   331  
   332  func TestNodeWebAlpine(t *testing.T) {
   333  	testWeb(t, "node-web", "alpine")
   334  }
   335  
   336  func TestNodeWebDebian(t *testing.T) {
   337  	testWeb(t, "node-web", "debian")
   338  }
   339  
   340  func TestNodeWebUbuntu(t *testing.T) {
   341  	testWeb(t, "node-web", "ubuntu")
   342  }
   343  
   344  // func TestNodeWebAmzn(t *testing.T) {
   345  // 	testWeb(t, "node-web", "amzn") // TODO this dockerfile is no longer building, debug.
   346  // }