blob: dddc90d21e20c48c0a45455b19fbe192ef24893c [file] [edit]
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package webbrowser
import (
"fmt"
"os"
"path/filepath"
"runtime"
"testing"
)
const fakeBrowserLaunchCmdOutputEnvName = "OPENTOFU_WEBBROWSER_EXEC_TEST_OUTPUT"
// TestMain overrides the test entrypoint so that we can reuse the test
// executable as a fake browser launcher command when testing
// [NewExecLauncher].
func TestMain(m *testing.M) {
if f := os.Getenv(fakeBrowserLaunchCmdOutputEnvName); f != "" {
err := fakeBrowserLauncherCommand(f)
if err != nil {
fmt.Fprintf(os.Stderr, "fake browser launcher failed: %s", err)
os.Exit(1)
}
os.Exit(0)
}
os.Exit(m.Run())
}
func fakeBrowserLauncherCommand(outputFilename string) error {
// The "exec" browser launcher must pass the URL to open in the
// first argument to the executable it launches.
url := os.Args[1]
return os.WriteFile(outputFilename, []byte(url), os.ModePerm)
}
func TestExecLauncher(t *testing.T) {
// For this test we re-use the text executable as a fake browser-launching
// program, through the special logic in [TestMain].
fakeExec := os.Args[0]
tempDir := t.TempDir()
outputFile := filepath.Join(tempDir, "browser-exec-launcher-test")
t.Setenv(fakeBrowserLaunchCmdOutputEnvName, outputFile)
launcher := NewExecLauncher(fakeExec)
err := launcher.OpenURL("http://example.com/")
if err != nil {
t.Fatal(err)
}
result, err := os.ReadFile(outputFile)
if err != nil {
t.Fatal(err)
}
if got, want := string(result), "http://example.com/"; got != want {
t.Errorf("wrong URL written to output file\ngot: %s\nwant: %s", got, want)
}
}
func TestParseBrowserEnv_success(t *testing.T) {
// ParseBrowserEnv only actually needs to work on Unix-like systems, so
// the test scenario below is not written to be portable.
// There's no runtime equivalent of the "unix" build tag, so we just
// explicitly test the two main Unix OSes we support here.
if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
t.Skip("ParseBrowserEnv is only for unix systems")
}
tmpDir := t.TempDir()
fakeExec := filepath.Join(tmpDir, "fake-launch-browser")
err := os.WriteFile(fakeExec, []byte(`not a real program`), 0755)
if err != nil {
// NOTE: This test requires the temp directory to be somewhere that
// allows executables, so this won't work if the temp directory is
// on a "noexec" mount on a Unix-style system.
t.Fatal(err)
}
// Temporarily we'll reset the search path to just our temp directory
t.Setenv("PATH", tmpDir)
result := ParseBrowserEnv("fake-launch-browser")
if result == "" {
t.Fatal("failed to find fake executable")
}
if got, want := filepath.Base(result), "fake-launch-browser"; got != want {
t.Fatalf("returned path %q has wrong basename %q; want %q", result, got, want)
}
}
func TestParseBrowserEnv_empty(t *testing.T) {
result := ParseBrowserEnv("")
if result != "" {
t.Errorf("returned %q, but wanted empty string", result)
}
}
func TestParseBrowserEnv_esrComplexSpec(t *testing.T) {
// The following tests with a strings following the more complex
// interpretation of BROWSER from http://www.catb.org/~esr/BROWSER/ , which
// OpenTofu intentionally doesn't support and so should be treated as
// if the environment variable isn't set at all.
t.Run(`with %s`, func(t *testing.T) {
// The esr proposal calls for checking whether there's a %s sequence
// in the value and then, if so, substituting the URL there and then
// passing the entire result to a shell. This is the main thing that
// different implementations did inconsistently, because it's
// unspecified whether the %s should be placed in quotes in the
// environment variable, if those quotes should be inserted by the
// program acting on the variable, or if some other shell escaping
// strategy should be used instead. We just ignore this form entirely
// because it's apparently not commonly used and it's unclear how
// to implement it without causing security problems.
result := ParseBrowserEnv("example %s")
if result != "" {
t.Errorf("returned %q, but wanted empty string", result)
}
})
t.Run("multiple commands", func(t *testing.T) {
// The esr proposal calls for splitting the string on semicolon
// and trying one command at a time until one succeeds. That's
// ambiguous with there being a single command whose path contains
// a semicolon, so we just try to treat it as a single command and
// ignore the value if that doesn't work. In practice the need for
// multiple options to try tends to be met instead by setting BROWSER
// to refer to a wrapper script that deals with the selection policy,
// which is the pattern OpenTofu supports.
result := ParseBrowserEnv("example1;example2")
if result != "" {
t.Errorf("returned %q, but wanted empty string", result)
}
})
}