Refactor Browsh Go code into package

This helps with isolating the tests and refactoring the code into
separate files.
pull/69/head
Thomas Buckley-Houston 2018-04-01 16:31:25 +08:00
rodzic 640f191e61
commit c0a79caf4e
8 zmienionych plików z 181 dodań i 155 usunięć

2
.gitignore vendored
Wyświetl plik

@ -7,7 +7,7 @@ interfacer/vendor
interfacer/dist
interfacer/interfacer
interfacer/browsh
interfacer/webextension.go
webextension.go
webext/node_modules
webext/dist/*

Wyświetl plik

@ -36,7 +36,7 @@ script:
- cd $REPO_ROOT/webext && npm test
- cd $REPO_ROOT/interfacer && ./contrib/run_tests.sh
after_failure:
- cat $REPO_ROOT/interfacer/debug.log
- cat $REPO_ROOT/interfacer/test/debug.log
after_success:
- $REPO_ROOT/contrib/release_if_new_version.sh

Wyświetl plik

@ -1,90 +0,0 @@
package main
import (
"bufio"
"os/exec"
"strings"
"testing"
"time"
"github.com/gdamore/tcell"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var simScreen tcell.SimulationScreen
var rootDir = shell("git rev-parse --show-toplevel")
var browserFingerprint = " ← | x | "
func TestIntegration(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Integration tests")
}
func startWERFirefox() {
args := []string{
"run",
"--firefox=" + rootDir + "/webext/contrib/firefoxheadless.sh",
"--verbose",
"--no-reload",
"--url=http://www.something.com/",
}
firefoxProcess := exec.Command(rootDir+"/webext/node_modules/.bin/web-ext", args...)
firefoxProcess.Dir = rootDir + "/webext/dist/"
defer firefoxProcess.Process.Kill()
stdout, err := firefoxProcess.StdoutPipe()
if err != nil {
shutdown(err)
}
if err := firefoxProcess.Start(); err != nil {
shutdown(err)
}
in := bufio.NewScanner(stdout)
for in.Scan() {
if strings.Contains(in.Text(), "JavaScript strict") ||
strings.Contains(in.Text(), "D-BUS") ||
strings.Contains(in.Text(), "dbus") {
continue
}
log("FF-CONSOLE: " + in.Text())
}
}
func GetFrameText() string {
var text string
cells, _, _ := simScreen.GetContents()
for _, element := range cells {
text += string(element.Runes)
}
return text
}
var _ = Describe("Integration", func() {
BeforeEach(func() {
var count = 0
simScreen = tcell.NewSimulationScreen("UTF-8")
go start(simScreen)
go startWERFirefox()
simScreen.SetSize(80, 30)
for {
if count > 10 {
break
}
time.Sleep(1 * time.Second)
if (strings.Contains(GetFrameText(), browserFingerprint)) {
break
}
count++
}
})
AfterEach(func() {
shell(rootDir + "/webext/contrib/firefoxheadless.sh kill")
})
Describe("Showing a basic webpage", func() {
It("have the right text", func() {
Expect(GetFrameText()).To(ContainSubstring("Something"))
})
})
})

Wyświetl plik

@ -1,4 +1,4 @@
#!/bin/bash
set -e
go test browsh.go browsh_test.go webextension.go -args -use-existing-ff -debug
go test test/browsh_test.go

Wyświetl plik

@ -1,5 +0,0 @@
package main
func main() {
ttyStart()
}

Wyświetl plik

@ -1,4 +1,4 @@
package main
package browsh
import (
"bufio"
@ -15,7 +15,6 @@ import (
"strings"
"strconv"
"time"
"math/rand"
"unicode"
// TCell seems to be one of the best projects in any language for handling terminal
@ -82,49 +81,53 @@ var (
func setupLogging() {
dir, err := os.Getwd()
if err != nil {
shutdown(err)
Shutdown(err)
}
logfile = fmt.Sprintf(filepath.Join(dir, "debug.log"))
if _, err := os.Stat(logfile); err == nil {
os.Truncate(logfile, 0)
}
if err != nil {
shutdown(err)
Shutdown(err)
}
}
func log(msg string) {
// Log ... general purpose logger
func Log(msg string) {
if !*isDebug {
return
}
f, oErr := os.OpenFile(logfile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if oErr != nil {
shutdown(oErr)
Shutdown(oErr)
}
defer f.Close()
msg = msg + "\n"
if _, wErr := f.WriteString(msg); wErr != nil {
shutdown(wErr)
Shutdown(wErr)
}
}
// Write a simple text string to the screen. Not for use in the browser frames
// themselves. If you want anything to appear in the browser then must be done
// themselves. If you want anything to appear in the browser that must be done
// through the webextension.
func writeString(x, y int, str string) {
var defaultColours = tcell.StyleDefault
rgb := tcell.NewHexColor(int32(rand.Int() & 0xffffff))
rgb := tcell.NewHexColor(int32(0xffffff))
defaultColours.Foreground(rgb)
defaultColours.Background(tcell.ColorRed)
for _, c := range str {
screen.SetContent(x, y, c, nil, defaultColours)
x++
}
screen.Sync()
}
func initialise() {
func initialise(isTesting bool) {
flag.Parse()
if isTesting {
*isDebug = true
}
setupTcell()
setupLogging()
}
@ -139,7 +142,8 @@ func setupTcell() {
screen.Clear()
}
func shutdown(err error) {
// Shutdown ... Cleanly Shutdown browsh
func Shutdown(err error) {
exitCode := 0
screen.Fini()
if err.Error() != "normal" {
@ -147,7 +151,7 @@ func shutdown(err error) {
println(err.Error())
}
out := err.(*errors.Error).ErrorStack()
log(fmt.Sprintf(out))
Log(fmt.Sprintf(out))
os.Exit(exitCode)
}
@ -165,7 +169,7 @@ func readStdin() {
if !*isUseExistingFirefox {
quitFirefox()
}
shutdown(errors.New("normal"))
Shutdown(errors.New("normal"))
}
eventMap := map[string]interface{}{
"key": int(ev.Key()),
@ -194,7 +198,7 @@ func readStdin() {
func sendMessageToWebExtension(message string) {
if (!isConnectedToWebExtension) {
log("Webextension not connected. Message not sent: " + message)
Log("Webextension not connected. Message not sent: " + message)
return
}
stdinChannel <- message
@ -207,11 +211,11 @@ func webSocketReader(ws *websocket.Conn) {
handleWebextensionCommand(message)
if err != nil {
if websocket.IsCloseError(err, websocket.CloseGoingAway) {
log("Socket reader detected that the browser closed the websocket")
Log("Socket reader detected that the browser closed the websocket")
triggerSocketWriterClose()
return
}
shutdown(err)
Shutdown(err)
}
}
}
@ -226,7 +230,7 @@ func handleWebextensionCommand(message []byte) {
case "/screenshot":
saveScreenshot(parts[1])
default:
log("WEBEXT: " + string(message))
Log("WEBEXT: " + string(message))
}
}
@ -237,7 +241,7 @@ func parseJSONframe(jsonString string) []string {
var frame []string
jsonBytes := []byte(jsonString)
if err := json.Unmarshal(jsonBytes, &frame); err != nil {
shutdown(err)
Shutdown(err)
}
return frame
}
@ -252,8 +256,8 @@ func renderFrame(frame []string) {
var runeChars []rune
width, height := screen.Size()
if (width * height * 7 != len(frame)) {
log("Not rendering frame: current frame is not the same size as the screen")
log(fmt.Sprintf("screen: %d, frame: %d", width * height * 7, len(frame)))
Log("Not rendering frame: current frame is not the same size as the screen")
Log(fmt.Sprintf("screen: %d, frame: %d", width * height * 7, len(frame)))
return
}
index := 0
@ -286,7 +290,7 @@ func getRGBColor(frame []string, index int) tcell.Color {
func toInt32(char string) int32 {
i, err := strconv.ParseInt(char, 10, 32)
if err != nil {
shutdown(err)
Shutdown(err)
}
return int32(i)
}
@ -294,21 +298,21 @@ func toInt32(char string) int32 {
func saveScreenshot(base64String string) {
dec, err := base64.StdEncoding.DecodeString(base64String)
if err != nil {
shutdown(err)
Shutdown(err)
}
file, err := ioutil.TempFile(os.TempDir(), "browsh-screenshot")
if err != nil {
shutdown(err)
Shutdown(err)
}
if _, err := file.Write(dec); err != nil {
shutdown(err)
Shutdown(err)
}
if err := file.Sync(); err != nil {
shutdown(err)
Shutdown(err)
}
fullPath := file.Name() + ".jpg"
if err := os.Rename(file.Name(), fullPath); err != nil {
shutdown(err)
Shutdown(err)
}
message := "Screenshot saved to " + fullPath
sendMessageToWebExtension("/status," + message)
@ -330,22 +334,22 @@ func webSocketWriter(ws *websocket.Conn) {
defer ws.Close()
for {
message = <-stdinChannel
log(fmt.Sprintf("TTY sending: %s", message))
Log(fmt.Sprintf("TTY sending: %s", message))
if err := ws.WriteMessage(websocket.TextMessage, []byte(message)); err != nil {
if err == websocket.ErrCloseSent {
log("Socket writer detected that the browser closed the websocket")
Log("Socket writer detected that the browser closed the websocket")
return
}
shutdown(err)
Shutdown(err)
}
}
}
func webSocketServer(w http.ResponseWriter, r *http.Request) {
log("Incoming web request from browser")
Log("Incoming web request from browser")
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
shutdown(err)
Shutdown(err)
}
isConnectedToWebExtension = true
@ -373,7 +377,8 @@ func stripWhitespace(str string) string {
}, str)
}
func shell(command string) string {
// Shell ... Nice and easy shell commands
func Shell(command string) string {
parts := strings.Fields(command)
head := parts[0]
parts = parts[1:len(parts)]
@ -385,35 +390,69 @@ func shell(command string) string {
}
func startHeadlessFirefox() {
log("Starting Firefox in headless mode")
firefoxPath := shell("which " + *firefoxBinary)
Log("Starting Firefox in headless mode")
firefoxPath := Shell("which " + *firefoxBinary)
if _, err := os.Stat(firefoxPath); os.IsNotExist(err) {
shutdown(errors.New("Firefox command not found: " + *firefoxBinary))
Shutdown(errors.New("Firefox command not found: " + *firefoxBinary))
}
args := []string{"--marionette"}
if !*isFFGui {
args = append(args, "--headless")
}
if *useFFProfile != "default" {
log("Using profile: " + *useFFProfile)
Log("Using profile: " + *useFFProfile)
args = append(args, "-P", *useFFProfile)
} else {
profilePath := getConfigFolder()
log("Using default profile at: " + profilePath)
Log("Using default profile at: " + profilePath)
args = append(args, "--profile", profilePath)
}
firefoxProcess := exec.Command(*firefoxBinary, args...)
defer firefoxProcess.Process.Kill()
stdout, err := firefoxProcess.StdoutPipe()
if err != nil {
shutdown(err)
Shutdown(err)
}
if err := firefoxProcess.Start(); err != nil {
shutdown(err)
Shutdown(err)
}
in := bufio.NewScanner(stdout)
for in.Scan() {
log("FF-CONSOLE: " + in.Text())
Log("FF-CONSOLE: " + in.Text())
}
}
// Start Firefox via the `web-ext` CLI tool. This is for development and testing,
// because I haven't been able to recreate the way `web-ext` injects an unsigned
// extension.
func startWERFirefox() {
Log("Attempting to start headless Firefox with `web-ext`")
var rootDir = Shell("git rev-parse --show-toplevel")
args := []string{
"run",
"--firefox=" + rootDir + "/webext/contrib/firefoxheadless.sh",
"--verbose",
"--no-reload",
"--url=http://www.something.com/",
}
firefoxProcess := exec.Command(rootDir+"/webext/node_modules/.bin/web-ext", args...)
firefoxProcess.Dir = rootDir + "/webext/dist/"
defer firefoxProcess.Process.Kill()
stdout, err := firefoxProcess.StdoutPipe()
if err != nil {
Shutdown(err)
}
if err := firefoxProcess.Start(); err != nil {
Shutdown(err)
}
in := bufio.NewScanner(stdout)
for in.Scan() {
if strings.Contains(in.Text(), "JavaScript strict") ||
strings.Contains(in.Text(), "D-BUS") ||
strings.Contains(in.Text(), "dbus") {
continue
}
Log("FF-CONSOLE: " + in.Text())
}
}
@ -428,10 +467,10 @@ func startHeadlessFirefox() {
// I've used Marionette here, simply because it was easier to reverse engineer
// from the Python Marionette package.
func firefoxMarionette() {
log("Attempting to connect to Firefox Marionette")
Log("Attempting to connect to Firefox Marionette")
conn, err := net.Dial("tcp", "127.0.0.1:2828")
if err != nil {
shutdown(err)
Shutdown(err)
}
marionette = conn
readMarionette()
@ -443,7 +482,7 @@ func firefoxMarionette() {
func installWebextension() {
data, err := Asset("webext/dist/web-ext-artifacts/browsh.xpi")
if err != nil {
shutdown(err)
Shutdown(err)
}
file, err := ioutil.TempFile(os.TempDir(), "prefix")
defer os.Remove(file.Name())
@ -471,13 +510,13 @@ func readMarionette() {
buffer := make([]byte, 4096)
count, err := marionette.Read(buffer)
if err != nil {
shutdown(err)
Shutdown(err)
}
log("FF-MRNT: " + string(buffer[:count]))
Log("FF-MRNT: " + string(buffer[:count]))
}
func sendFirefoxCommand(command string, args map[string]interface{}) {
log("Sending `" + command + "` to Firefox Marionette")
Log("Sending `" + command + "` to Firefox Marionette")
fullCommand := []interface{}{0, ffCommandCount, command, args}
marshalled, _ := json.Marshal(fullCommand)
message := fmt.Sprintf("%d:%s", len(marshalled), marshalled)
@ -509,7 +548,7 @@ func beginTimeLimit() {
sendMessageToWebExtension("/status," + message)
time.Sleep(time.Duration(warningLength) * time.Second)
quitFirefox()
shutdown(errors.New("normal"))
Shutdown(errors.New("normal"))
}
// Note that everything executed in and from this function is not covered by the integration
@ -532,25 +571,33 @@ func quitFirefox() {
sendFirefoxCommand("quitApplication", map[string]interface{}{})
}
func start(injectedScreen tcell.Screen) {
// Start ... Start Browsh
func Start(injectedScreen tcell.Screen) {
var isTesting = fmt.Sprintf("%T", injectedScreen) == "*tcell.simscreen"
screen = injectedScreen
initialise()
initialise(isTesting)
if !*isUseExistingFirefox {
writeString(0, 0, "Starting Browsh, the modern terminal web browser...")
setupFirefox()
if isTesting {
writeString(0, 0, "Starting Browsh in test mode...")
go startWERFirefox()
} else {
writeString(0, 0, "Starting Browsh, the modern terminal web browser...")
setupFirefox()
}
} else {
writeString(0, 0, "Waiting for a Firefox instance to connect...")
}
log("Starting Browsh CLI client")
Log("Starting Browsh CLI client")
go readStdin()
http.HandleFunc("/", webSocketServer)
if err := http.ListenAndServe(*webSocketAddresss, nil); err != nil {
shutdown(err)
Shutdown(err)
}
log("Exiting at end of main()")
Log("Exiting at end of main()")
}
func ttyStart() {
// TtyStart ... Main entrypoint.
func TtyStart() {
// Hack to force true colours
// Follow: https://github.com/gdamore/tcell/pull/183
os.Setenv("TERM", "xterm-truecolor")
@ -560,5 +607,5 @@ func ttyStart() {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
start(realScreen)
Start(realScreen)
}

Wyświetl plik

@ -0,0 +1,7 @@
package main
import "browsh"
func main() {
browsh.TtyStart()
}

Wyświetl plik

@ -0,0 +1,67 @@
package main
import (
"strings"
"testing"
"time"
"strconv"
"fmt"
"github.com/gdamore/tcell"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"browsh"
)
var simScreen tcell.SimulationScreen
var startupWait = 10
var browserFingerprint = " ← | x | "
var rootDir = browsh.Shell("git rev-parse --show-toplevel")
func TestIntegration(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Integration tests")
}
func GetFrameText() string {
var text string
cells, _, _ := simScreen.GetContents()
for _, element := range cells {
text += string(element.Runes)
}
fmt.Println(text)
return text
}
var _ = Describe("Integration", func() {
BeforeEach(func() {
var count = 0
simScreen = tcell.NewSimulationScreen("UTF-8")
go browsh.Start(simScreen)
for {
if count > startupWait {
var message = "Couldn't find browsh " +
"startup signature within " +
strconv.Itoa(startupWait) +
" seconds"
panic(message)
}
time.Sleep(time.Second)
if (strings.Contains(GetFrameText(), browserFingerprint)) {
break
}
count++
}
})
AfterEach(func() {
browsh.Shell(rootDir + "/webext/contrib/firefoxheadless.sh kill")
})
Describe("Showing a basic webpage", func() {
It("have the right text", func() {
Expect(GetFrameText()).To(ContainSubstring("Something"))
})
})
})