...

Source file src/net/http/cgi/host_test.go

Documentation: net/http/cgi

		 1  // Copyright 2011 The Go Authors. All rights reserved.
		 2  // Use of this source code is governed by a BSD-style
		 3  // license that can be found in the LICENSE file.
		 4  
		 5  // Tests for package cgi
		 6  
		 7  package cgi
		 8  
		 9  import (
		10  	"bufio"
		11  	"bytes"
		12  	"fmt"
		13  	"io"
		14  	"net"
		15  	"net/http"
		16  	"net/http/httptest"
		17  	"os"
		18  	"os/exec"
		19  	"path/filepath"
		20  	"reflect"
		21  	"runtime"
		22  	"strconv"
		23  	"strings"
		24  	"testing"
		25  	"time"
		26  )
		27  
		28  func newRequest(httpreq string) *http.Request {
		29  	buf := bufio.NewReader(strings.NewReader(httpreq))
		30  	req, err := http.ReadRequest(buf)
		31  	if err != nil {
		32  		panic("cgi: bogus http request in test: " + httpreq)
		33  	}
		34  	req.RemoteAddr = "1.2.3.4:1234"
		35  	return req
		36  }
		37  
		38  func runCgiTest(t *testing.T, h *Handler,
		39  	httpreq string,
		40  	expectedMap map[string]string, checks ...func(reqInfo map[string]string)) *httptest.ResponseRecorder {
		41  	rw := httptest.NewRecorder()
		42  	req := newRequest(httpreq)
		43  	h.ServeHTTP(rw, req)
		44  	runResponseChecks(t, rw, expectedMap, checks...)
		45  	return rw
		46  }
		47  
		48  func runResponseChecks(t *testing.T, rw *httptest.ResponseRecorder,
		49  	expectedMap map[string]string, checks ...func(reqInfo map[string]string)) {
		50  	// Make a map to hold the test map that the CGI returns.
		51  	m := make(map[string]string)
		52  	m["_body"] = rw.Body.String()
		53  	linesRead := 0
		54  readlines:
		55  	for {
		56  		line, err := rw.Body.ReadString('\n')
		57  		switch {
		58  		case err == io.EOF:
		59  			break readlines
		60  		case err != nil:
		61  			t.Fatalf("unexpected error reading from CGI: %v", err)
		62  		}
		63  		linesRead++
		64  		trimmedLine := strings.TrimRight(line, "\r\n")
		65  		split := strings.SplitN(trimmedLine, "=", 2)
		66  		if len(split) != 2 {
		67  			t.Fatalf("Unexpected %d parts from invalid line number %v: %q; existing map=%v",
		68  				len(split), linesRead, line, m)
		69  		}
		70  		m[split[0]] = split[1]
		71  	}
		72  
		73  	for key, expected := range expectedMap {
		74  		got := m[key]
		75  		if key == "cwd" {
		76  			// For Windows. golang.org/issue/4645.
		77  			fi1, _ := os.Stat(got)
		78  			fi2, _ := os.Stat(expected)
		79  			if os.SameFile(fi1, fi2) {
		80  				got = expected
		81  			}
		82  		}
		83  		if got != expected {
		84  			t.Errorf("for key %q got %q; expected %q", key, got, expected)
		85  		}
		86  	}
		87  	for _, check := range checks {
		88  		check(m)
		89  	}
		90  }
		91  
		92  var cgiTested, cgiWorks bool
		93  
		94  func check(t *testing.T) {
		95  	if !cgiTested {
		96  		cgiTested = true
		97  		cgiWorks = exec.Command("./testdata/test.cgi").Run() == nil
		98  	}
		99  	if !cgiWorks {
	 100  		// No Perl on Windows, needed by test.cgi
	 101  		// TODO: make the child process be Go, not Perl.
	 102  		t.Skip("Skipping test: test.cgi failed.")
	 103  	}
	 104  }
	 105  
	 106  func TestCGIBasicGet(t *testing.T) {
	 107  	check(t)
	 108  	h := &Handler{
	 109  		Path: "testdata/test.cgi",
	 110  		Root: "/test.cgi",
	 111  	}
	 112  	expectedMap := map[string]string{
	 113  		"test":									"Hello CGI",
	 114  		"param-a":							 "b",
	 115  		"param-foo":						 "bar",
	 116  		"env-GATEWAY_INTERFACE": "CGI/1.1",
	 117  		"env-HTTP_HOST":				 "example.com",
	 118  		"env-PATH_INFO":				 "",
	 119  		"env-QUERY_STRING":			"foo=bar&a=b",
	 120  		"env-REMOTE_ADDR":			 "1.2.3.4",
	 121  		"env-REMOTE_HOST":			 "1.2.3.4",
	 122  		"env-REMOTE_PORT":			 "1234",
	 123  		"env-REQUEST_METHOD":		"GET",
	 124  		"env-REQUEST_URI":			 "/test.cgi?foo=bar&a=b",
	 125  		"env-SCRIPT_FILENAME":	 "testdata/test.cgi",
	 126  		"env-SCRIPT_NAME":			 "/test.cgi",
	 127  		"env-SERVER_NAME":			 "example.com",
	 128  		"env-SERVER_PORT":			 "80",
	 129  		"env-SERVER_SOFTWARE":	 "go",
	 130  	}
	 131  	replay := runCgiTest(t, h, "GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
	 132  
	 133  	if expected, got := "text/html", replay.Header().Get("Content-Type"); got != expected {
	 134  		t.Errorf("got a Content-Type of %q; expected %q", got, expected)
	 135  	}
	 136  	if expected, got := "X-Test-Value", replay.Header().Get("X-Test-Header"); got != expected {
	 137  		t.Errorf("got a X-Test-Header of %q; expected %q", got, expected)
	 138  	}
	 139  }
	 140  
	 141  func TestCGIEnvIPv6(t *testing.T) {
	 142  	check(t)
	 143  	h := &Handler{
	 144  		Path: "testdata/test.cgi",
	 145  		Root: "/test.cgi",
	 146  	}
	 147  	expectedMap := map[string]string{
	 148  		"test":									"Hello CGI",
	 149  		"param-a":							 "b",
	 150  		"param-foo":						 "bar",
	 151  		"env-GATEWAY_INTERFACE": "CGI/1.1",
	 152  		"env-HTTP_HOST":				 "example.com",
	 153  		"env-PATH_INFO":				 "",
	 154  		"env-QUERY_STRING":			"foo=bar&a=b",
	 155  		"env-REMOTE_ADDR":			 "2000::3000",
	 156  		"env-REMOTE_HOST":			 "2000::3000",
	 157  		"env-REMOTE_PORT":			 "12345",
	 158  		"env-REQUEST_METHOD":		"GET",
	 159  		"env-REQUEST_URI":			 "/test.cgi?foo=bar&a=b",
	 160  		"env-SCRIPT_FILENAME":	 "testdata/test.cgi",
	 161  		"env-SCRIPT_NAME":			 "/test.cgi",
	 162  		"env-SERVER_NAME":			 "example.com",
	 163  		"env-SERVER_PORT":			 "80",
	 164  		"env-SERVER_SOFTWARE":	 "go",
	 165  	}
	 166  
	 167  	rw := httptest.NewRecorder()
	 168  	req := newRequest("GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n")
	 169  	req.RemoteAddr = "[2000::3000]:12345"
	 170  	h.ServeHTTP(rw, req)
	 171  	runResponseChecks(t, rw, expectedMap)
	 172  }
	 173  
	 174  func TestCGIBasicGetAbsPath(t *testing.T) {
	 175  	check(t)
	 176  	pwd, err := os.Getwd()
	 177  	if err != nil {
	 178  		t.Fatalf("getwd error: %v", err)
	 179  	}
	 180  	h := &Handler{
	 181  		Path: pwd + "/testdata/test.cgi",
	 182  		Root: "/test.cgi",
	 183  	}
	 184  	expectedMap := map[string]string{
	 185  		"env-REQUEST_URI":		 "/test.cgi?foo=bar&a=b",
	 186  		"env-SCRIPT_FILENAME": pwd + "/testdata/test.cgi",
	 187  		"env-SCRIPT_NAME":		 "/test.cgi",
	 188  	}
	 189  	runCgiTest(t, h, "GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
	 190  }
	 191  
	 192  func TestPathInfo(t *testing.T) {
	 193  	check(t)
	 194  	h := &Handler{
	 195  		Path: "testdata/test.cgi",
	 196  		Root: "/test.cgi",
	 197  	}
	 198  	expectedMap := map[string]string{
	 199  		"param-a":						 "b",
	 200  		"env-PATH_INFO":			 "/extrapath",
	 201  		"env-QUERY_STRING":		"a=b",
	 202  		"env-REQUEST_URI":		 "/test.cgi/extrapath?a=b",
	 203  		"env-SCRIPT_FILENAME": "testdata/test.cgi",
	 204  		"env-SCRIPT_NAME":		 "/test.cgi",
	 205  	}
	 206  	runCgiTest(t, h, "GET /test.cgi/extrapath?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
	 207  }
	 208  
	 209  func TestPathInfoDirRoot(t *testing.T) {
	 210  	check(t)
	 211  	h := &Handler{
	 212  		Path: "testdata/test.cgi",
	 213  		Root: "/myscript/",
	 214  	}
	 215  	expectedMap := map[string]string{
	 216  		"env-PATH_INFO":			 "bar",
	 217  		"env-QUERY_STRING":		"a=b",
	 218  		"env-REQUEST_URI":		 "/myscript/bar?a=b",
	 219  		"env-SCRIPT_FILENAME": "testdata/test.cgi",
	 220  		"env-SCRIPT_NAME":		 "/myscript/",
	 221  	}
	 222  	runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
	 223  }
	 224  
	 225  func TestDupHeaders(t *testing.T) {
	 226  	check(t)
	 227  	h := &Handler{
	 228  		Path: "testdata/test.cgi",
	 229  	}
	 230  	expectedMap := map[string]string{
	 231  		"env-REQUEST_URI":		 "/myscript/bar?a=b",
	 232  		"env-SCRIPT_FILENAME": "testdata/test.cgi",
	 233  		"env-HTTP_COOKIE":		 "nom=NOM; yum=YUM",
	 234  		"env-HTTP_X_FOO":			"val1, val2",
	 235  	}
	 236  	runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\n"+
	 237  		"Cookie: nom=NOM\n"+
	 238  		"Cookie: yum=YUM\n"+
	 239  		"X-Foo: val1\n"+
	 240  		"X-Foo: val2\n"+
	 241  		"Host: example.com\n\n",
	 242  		expectedMap)
	 243  }
	 244  
	 245  // Issue 16405: CGI+http.Transport differing uses of HTTP_PROXY.
	 246  // Verify we don't set the HTTP_PROXY environment variable.
	 247  // Hope nobody was depending on it. It's not a known header, though.
	 248  func TestDropProxyHeader(t *testing.T) {
	 249  	check(t)
	 250  	h := &Handler{
	 251  		Path: "testdata/test.cgi",
	 252  	}
	 253  	expectedMap := map[string]string{
	 254  		"env-REQUEST_URI":		 "/myscript/bar?a=b",
	 255  		"env-SCRIPT_FILENAME": "testdata/test.cgi",
	 256  		"env-HTTP_X_FOO":			"a",
	 257  	}
	 258  	runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\n"+
	 259  		"X-Foo: a\n"+
	 260  		"Proxy: should_be_stripped\n"+
	 261  		"Host: example.com\n\n",
	 262  		expectedMap,
	 263  		func(reqInfo map[string]string) {
	 264  			if v, ok := reqInfo["env-HTTP_PROXY"]; ok {
	 265  				t.Errorf("HTTP_PROXY = %q; should be absent", v)
	 266  			}
	 267  		})
	 268  }
	 269  
	 270  func TestPathInfoNoRoot(t *testing.T) {
	 271  	check(t)
	 272  	h := &Handler{
	 273  		Path: "testdata/test.cgi",
	 274  		Root: "",
	 275  	}
	 276  	expectedMap := map[string]string{
	 277  		"env-PATH_INFO":			 "/bar",
	 278  		"env-QUERY_STRING":		"a=b",
	 279  		"env-REQUEST_URI":		 "/bar?a=b",
	 280  		"env-SCRIPT_FILENAME": "testdata/test.cgi",
	 281  		"env-SCRIPT_NAME":		 "/",
	 282  	}
	 283  	runCgiTest(t, h, "GET /bar?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
	 284  }
	 285  
	 286  func TestCGIBasicPost(t *testing.T) {
	 287  	check(t)
	 288  	postReq := `POST /test.cgi?a=b HTTP/1.0
	 289  Host: example.com
	 290  Content-Type: application/x-www-form-urlencoded
	 291  Content-Length: 15
	 292  
	 293  postfoo=postbar`
	 294  	h := &Handler{
	 295  		Path: "testdata/test.cgi",
	 296  		Root: "/test.cgi",
	 297  	}
	 298  	expectedMap := map[string]string{
	 299  		"test":							 "Hello CGI",
	 300  		"param-postfoo":			"postbar",
	 301  		"env-REQUEST_METHOD": "POST",
	 302  		"env-CONTENT_LENGTH": "15",
	 303  		"env-REQUEST_URI":		"/test.cgi?a=b",
	 304  	}
	 305  	runCgiTest(t, h, postReq, expectedMap)
	 306  }
	 307  
	 308  func chunk(s string) string {
	 309  	return fmt.Sprintf("%x\r\n%s\r\n", len(s), s)
	 310  }
	 311  
	 312  // The CGI spec doesn't allow chunked requests.
	 313  func TestCGIPostChunked(t *testing.T) {
	 314  	check(t)
	 315  	postReq := `POST /test.cgi?a=b HTTP/1.1
	 316  Host: example.com
	 317  Content-Type: application/x-www-form-urlencoded
	 318  Transfer-Encoding: chunked
	 319  
	 320  ` + chunk("postfoo") + chunk("=") + chunk("postbar") + chunk("")
	 321  
	 322  	h := &Handler{
	 323  		Path: "testdata/test.cgi",
	 324  		Root: "/test.cgi",
	 325  	}
	 326  	expectedMap := map[string]string{}
	 327  	resp := runCgiTest(t, h, postReq, expectedMap)
	 328  	if got, expected := resp.Code, http.StatusBadRequest; got != expected {
	 329  		t.Fatalf("Expected %v response code from chunked request body; got %d",
	 330  			expected, got)
	 331  	}
	 332  }
	 333  
	 334  func TestRedirect(t *testing.T) {
	 335  	check(t)
	 336  	h := &Handler{
	 337  		Path: "testdata/test.cgi",
	 338  		Root: "/test.cgi",
	 339  	}
	 340  	rec := runCgiTest(t, h, "GET /test.cgi?loc=http://foo.com/ HTTP/1.0\nHost: example.com\n\n", nil)
	 341  	if e, g := 302, rec.Code; e != g {
	 342  		t.Errorf("expected status code %d; got %d", e, g)
	 343  	}
	 344  	if e, g := "http://foo.com/", rec.Header().Get("Location"); e != g {
	 345  		t.Errorf("expected Location header of %q; got %q", e, g)
	 346  	}
	 347  }
	 348  
	 349  func TestInternalRedirect(t *testing.T) {
	 350  	check(t)
	 351  	baseHandler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
	 352  		fmt.Fprintf(rw, "basepath=%s\n", req.URL.Path)
	 353  		fmt.Fprintf(rw, "remoteaddr=%s\n", req.RemoteAddr)
	 354  	})
	 355  	h := &Handler{
	 356  		Path:								"testdata/test.cgi",
	 357  		Root:								"/test.cgi",
	 358  		PathLocationHandler: baseHandler,
	 359  	}
	 360  	expectedMap := map[string]string{
	 361  		"basepath":	 "/foo",
	 362  		"remoteaddr": "1.2.3.4:1234",
	 363  	}
	 364  	runCgiTest(t, h, "GET /test.cgi?loc=/foo HTTP/1.0\nHost: example.com\n\n", expectedMap)
	 365  }
	 366  
	 367  // TestCopyError tests that we kill the process if there's an error copying
	 368  // its output. (for example, from the client having gone away)
	 369  func TestCopyError(t *testing.T) {
	 370  	check(t)
	 371  	if runtime.GOOS == "windows" {
	 372  		t.Skipf("skipping test on %q", runtime.GOOS)
	 373  	}
	 374  	h := &Handler{
	 375  		Path: "testdata/test.cgi",
	 376  		Root: "/test.cgi",
	 377  	}
	 378  	ts := httptest.NewServer(h)
	 379  	defer ts.Close()
	 380  
	 381  	conn, err := net.Dial("tcp", ts.Listener.Addr().String())
	 382  	if err != nil {
	 383  		t.Fatal(err)
	 384  	}
	 385  	req, _ := http.NewRequest("GET", "http://example.com/test.cgi?bigresponse=1", nil)
	 386  	err = req.Write(conn)
	 387  	if err != nil {
	 388  		t.Fatalf("Write: %v", err)
	 389  	}
	 390  
	 391  	res, err := http.ReadResponse(bufio.NewReader(conn), req)
	 392  	if err != nil {
	 393  		t.Fatalf("ReadResponse: %v", err)
	 394  	}
	 395  
	 396  	pidstr := res.Header.Get("X-CGI-Pid")
	 397  	if pidstr == "" {
	 398  		t.Fatalf("expected an X-CGI-Pid header in response")
	 399  	}
	 400  	pid, err := strconv.Atoi(pidstr)
	 401  	if err != nil {
	 402  		t.Fatalf("invalid X-CGI-Pid value")
	 403  	}
	 404  
	 405  	var buf [5000]byte
	 406  	n, err := io.ReadFull(res.Body, buf[:])
	 407  	if err != nil {
	 408  		t.Fatalf("ReadFull: %d bytes, %v", n, err)
	 409  	}
	 410  
	 411  	childRunning := func() bool {
	 412  		return isProcessRunning(pid)
	 413  	}
	 414  
	 415  	if !childRunning() {
	 416  		t.Fatalf("pre-conn.Close, expected child to be running")
	 417  	}
	 418  	conn.Close()
	 419  
	 420  	tries := 0
	 421  	for tries < 25 && childRunning() {
	 422  		time.Sleep(50 * time.Millisecond * time.Duration(tries))
	 423  		tries++
	 424  	}
	 425  	if childRunning() {
	 426  		t.Fatalf("post-conn.Close, expected child to be gone")
	 427  	}
	 428  }
	 429  
	 430  func TestDirUnix(t *testing.T) {
	 431  	check(t)
	 432  	if runtime.GOOS == "windows" {
	 433  		t.Skipf("skipping test on %q", runtime.GOOS)
	 434  	}
	 435  	cwd, _ := os.Getwd()
	 436  	h := &Handler{
	 437  		Path: "testdata/test.cgi",
	 438  		Root: "/test.cgi",
	 439  		Dir:	cwd,
	 440  	}
	 441  	expectedMap := map[string]string{
	 442  		"cwd": cwd,
	 443  	}
	 444  	runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
	 445  
	 446  	cwd, _ = os.Getwd()
	 447  	cwd = filepath.Join(cwd, "testdata")
	 448  	h = &Handler{
	 449  		Path: "testdata/test.cgi",
	 450  		Root: "/test.cgi",
	 451  	}
	 452  	expectedMap = map[string]string{
	 453  		"cwd": cwd,
	 454  	}
	 455  	runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
	 456  }
	 457  
	 458  func findPerl(t *testing.T) string {
	 459  	t.Helper()
	 460  	perl, err := exec.LookPath("perl")
	 461  	if err != nil {
	 462  		t.Skip("Skipping test: perl not found.")
	 463  	}
	 464  	perl, _ = filepath.Abs(perl)
	 465  
	 466  	cmd := exec.Command(perl, "-e", "print 123")
	 467  	cmd.Env = []string{"PATH=/garbage"}
	 468  	out, err := cmd.Output()
	 469  	if err != nil || string(out) != "123" {
	 470  		t.Skipf("Skipping test: %s is not functional", perl)
	 471  	}
	 472  	return perl
	 473  }
	 474  
	 475  func TestDirWindows(t *testing.T) {
	 476  	if runtime.GOOS != "windows" {
	 477  		t.Skip("Skipping windows specific test.")
	 478  	}
	 479  
	 480  	cgifile, _ := filepath.Abs("testdata/test.cgi")
	 481  
	 482  	perl := findPerl(t)
	 483  
	 484  	cwd, _ := os.Getwd()
	 485  	h := &Handler{
	 486  		Path: perl,
	 487  		Root: "/test.cgi",
	 488  		Dir:	cwd,
	 489  		Args: []string{cgifile},
	 490  		Env:	[]string{"SCRIPT_FILENAME=" + cgifile},
	 491  	}
	 492  	expectedMap := map[string]string{
	 493  		"cwd": cwd,
	 494  	}
	 495  	runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
	 496  
	 497  	// If not specify Dir on windows, working directory should be
	 498  	// base directory of perl.
	 499  	cwd, _ = filepath.Split(perl)
	 500  	if cwd != "" && cwd[len(cwd)-1] == filepath.Separator {
	 501  		cwd = cwd[:len(cwd)-1]
	 502  	}
	 503  	h = &Handler{
	 504  		Path: perl,
	 505  		Root: "/test.cgi",
	 506  		Args: []string{cgifile},
	 507  		Env:	[]string{"SCRIPT_FILENAME=" + cgifile},
	 508  	}
	 509  	expectedMap = map[string]string{
	 510  		"cwd": cwd,
	 511  	}
	 512  	runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
	 513  }
	 514  
	 515  func TestEnvOverride(t *testing.T) {
	 516  	check(t)
	 517  	cgifile, _ := filepath.Abs("testdata/test.cgi")
	 518  
	 519  	perl := findPerl(t)
	 520  
	 521  	cwd, _ := os.Getwd()
	 522  	h := &Handler{
	 523  		Path: perl,
	 524  		Root: "/test.cgi",
	 525  		Dir:	cwd,
	 526  		Args: []string{cgifile},
	 527  		Env: []string{
	 528  			"SCRIPT_FILENAME=" + cgifile,
	 529  			"REQUEST_URI=/foo/bar",
	 530  			"PATH=/wibble"},
	 531  	}
	 532  	expectedMap := map[string]string{
	 533  		"cwd":								 cwd,
	 534  		"env-SCRIPT_FILENAME": cgifile,
	 535  		"env-REQUEST_URI":		 "/foo/bar",
	 536  		"env-PATH":						"/wibble",
	 537  	}
	 538  	runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
	 539  }
	 540  
	 541  func TestHandlerStderr(t *testing.T) {
	 542  	check(t)
	 543  	var stderr bytes.Buffer
	 544  	h := &Handler{
	 545  		Path:	 "testdata/test.cgi",
	 546  		Root:	 "/test.cgi",
	 547  		Stderr: &stderr,
	 548  	}
	 549  
	 550  	rw := httptest.NewRecorder()
	 551  	req := newRequest("GET /test.cgi?writestderr=1 HTTP/1.0\nHost: example.com\n\n")
	 552  	h.ServeHTTP(rw, req)
	 553  	if got, want := stderr.String(), "Hello, stderr!\n"; got != want {
	 554  		t.Errorf("Stderr = %q; want %q", got, want)
	 555  	}
	 556  }
	 557  
	 558  func TestRemoveLeadingDuplicates(t *testing.T) {
	 559  	tests := []struct {
	 560  		env	[]string
	 561  		want []string
	 562  	}{
	 563  		{
	 564  			env:	[]string{"a=b", "b=c", "a=b2"},
	 565  			want: []string{"b=c", "a=b2"},
	 566  		},
	 567  		{
	 568  			env:	[]string{"a=b", "b=c", "d", "e=f"},
	 569  			want: []string{"a=b", "b=c", "d", "e=f"},
	 570  		},
	 571  	}
	 572  	for _, tt := range tests {
	 573  		got := removeLeadingDuplicates(tt.env)
	 574  		if !reflect.DeepEqual(got, tt.want) {
	 575  			t.Errorf("removeLeadingDuplicates(%q) = %q; want %q", tt.env, got, tt.want)
	 576  		}
	 577  	}
	 578  }
	 579  

View as plain text