diff --git a/Makefile b/Makefile index 28d009ca5ffa87debf1cfa6f83403c32ca01254f..7d284b4656fed01da77dac9eb58a17577c7972b9 100644 --- a/Makefile +++ b/Makefile @@ -14,8 +14,8 @@ VERSION ?= $(shell git describe) # go paths GOPATH = $(shell go env GOPATH) -SYSTRAY = 0xacab.org/leap/bitmask-vpn -GOSYSTRAY = ${GOPATH}/src/${SYSTRAY} +TARGET_GOLIB=lib/libgoshim.a +SOURCE_GOLIB=gui/backend.go # detect OS, we use it for dependencies UNAME = $(shell uname -s) @@ -81,6 +81,15 @@ build_%: test: @go test -tags "integration $(TAGS)" ./... +golib: + CGO_ENABLED=1 go build -buildmode=c-archive -o ${TARGET_GOLIB} ${SOURCE_GOLIB} + +test_ui: golib + @qmake -o tests/Makefile test.pro + @make -C tests clean + @make -C tests + @./tests/build/test_ui + build_win: powershell -Command '$$version=git describe --tags; go build -ldflags "-H windowsgui -X main.version=$$version" ./cmd/*' diff --git a/README.md b/README.md index 07593692ced900fba1a786f713d1673ce0a1d797..cb20665b701fe6882f03a54f6c66377c841e6d7d 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,14 @@ Linux ./build.sh +Running tests +------------- + +sudo apt install qml-module-qttest +make test +make test_ui + + i18n ---- diff --git a/bitmask.pro b/bitmask.pro index 115aec710bee34a51d65f072f226e9f8f0c44314..7a294838d4a5a661a0f88eadad5a07bc65bbef02 100644 --- a/bitmask.pro +++ b/bitmask.pro @@ -33,7 +33,6 @@ MOC_DIR = release/.moc RCC_DIR = release/.rcc UI_DIR = release/.ui -Release:DESTDIR = release Release:DESTDIR = release Release:OBJECTS_DIR = release/.obj Release:MOC_DIR = release/.moc diff --git a/gui/backend.go b/gui/backend.go index f7816ee8ce41137cc4ab7c923c91bfd94d0fa653..4a73cc261226a3232d6981f86a9f57e76831af98 100644 --- a/gui/backend.go +++ b/gui/backend.go @@ -44,7 +44,16 @@ func SubscribeToEvent(event string, f unsafe.Pointer) { //export InitializeBitmaskContext func InitializeBitmaskContext() { - backend.InitializeBitmaskContext() + opts := &backend.InitOpts{} + backend.InitializeBitmaskContext(opts) +} + +//export InitializeTestBitmaskContext +func InitializeTestBitmaskContext() { + opts := &backend.InitOpts{} + opts.SkipLaunch = true + backend.InitializeBitmaskContext(opts) + backend.EnableMockBackend() } //export RefreshContext diff --git a/gui/qml/main.qml b/gui/qml/main.qml index efe0111a53ca6842ba83a1c86c24a790da4ab67e..4aab7f19a3cade8f65e36589c8b88f079af27547 100644 --- a/gui/qml/main.qml +++ b/gui/qml/main.qml @@ -9,7 +9,7 @@ ApplicationWindow { id: app visible: false - property var ctx + property var ctx Connections { target: jsonModel diff --git a/pkg/backend/api.go b/pkg/backend/api.go index a19fd4043bead94f9c12aceb5befae2c4b55c2a0..fea38dbcaaa14df1db4d5030c643c515f950dc72 100644 --- a/pkg/backend/api.go +++ b/pkg/backend/api.go @@ -45,10 +45,18 @@ func SubscribeToEvent(event string, f unsafe.Pointer) { subscribe(event, f) } -func InitializeBitmaskContext() { +type InitOpts struct { + Provider string + AppName string + SkipLaunch bool +} + +func InitializeBitmaskContext(opts *InitOpts) { p := bitmask.GetConfiguredProvider() + opts.Provider = p.Provider + opts.AppName = p.AppName - initOnce.Do(func() { initializeContext(p.Provider, p.AppName) }) + initOnce.Do(func() { initializeContext(opts) }) runDonationReminder() go ctx.updateStatus() } @@ -62,7 +70,7 @@ func InstallHelpers() { pickle.InstallHelpers() } -func MockUIInteraction() { - log.Println("mocking ui interaction on port 8080. \nTry 'curl localhost:8080/{on|off|failed}' to toggle status.") - go mockUI() +func EnableMockBackend() { + log.Println("[+] Mocking ui interaction on port 8080. \nTry 'curl localhost:8080/{on|off|failed}' to toggle status.") + go enableMockBackend() } diff --git a/pkg/backend/init.go b/pkg/backend/init.go index 5abb05ecd122a622ca17ecd7c6cd147b3da12b0b..79efdc7878f90e0867ee5ffe83946bc837b74210 100644 --- a/pkg/backend/init.go +++ b/pkg/backend/init.go @@ -12,11 +12,11 @@ import ( // initializeContext initializes an empty connStatus and assigns it to the // global ctx holder. This is expected to be called only once, so the public // api uses the sync.Once primitive to call this. -func initializeContext(provider, appName string) { +func initializeContext(opts *InitOpts) { var st status = off ctx = &connectionCtx{ - AppName: appName, - Provider: provider, + AppName: opts.AppName, + Provider: opts.Provider, TosURL: config.TosURL, HelpURL: config.HelpURL, DonateURL: config.DonateURL, @@ -28,7 +28,7 @@ func initializeContext(provider, appName string) { errCh := make(chan string) go trigger(OnStatusChanged) go checkErrors(errCh) - initializeBitmask(errCh) + initializeBitmask(errCh, opts) } func checkErrors(errCh chan string) { @@ -39,14 +39,14 @@ func checkErrors(errCh chan string) { } } -func initializeBitmask(errCh chan string) { +func initializeBitmask(errCh chan string, opts *InitOpts) { if ctx == nil { log.Println("bug: cannot initialize bitmask, ctx is nil!") os.Exit(1) } bitmask.InitializeLogger() - b, err := bitmask.InitializeBitmask() + b, err := bitmask.InitializeBitmask(opts.SkipLaunch) if err != nil { log.Println("error: cannot initialize bitmask") errCh <- err.Error() diff --git a/pkg/backend/mocks.go b/pkg/backend/mocks.go index a8ede7363d07e9e198f60a150674ee1d9eb984a3..226fa4e6f7e9e4ef36716b1e51ee5be279f1c6fc 100644 --- a/pkg/backend/mocks.go +++ b/pkg/backend/mocks.go @@ -9,6 +9,14 @@ import ( * should also show a good way of writing functionality tests just for the Qml * layer */ +func enableMockBackend() { + log.Println("[+] You should not use this in production!") + http.HandleFunc("/on", mockUIOn) + http.HandleFunc("/off", mockUIOff) + http.HandleFunc("/failed", mockUIFailed) + http.ListenAndServe(":8080", nil) +} + func mockUIOn(w http.ResponseWriter, r *http.Request) { log.Println("changing status: on") setStatus(on) @@ -23,10 +31,3 @@ func mockUIFailed(w http.ResponseWriter, r *http.Request) { log.Println("changing status: failed") setStatus(failed) } - -func mockUI() { - http.HandleFunc("/on", mockUIOn) - http.HandleFunc("/off", mockUIOff) - http.HandleFunc("/failed", mockUIFailed) - http.ListenAndServe(":8080", nil) -} diff --git a/pkg/bitmask/init.go b/pkg/bitmask/init.go index 33a5911fa2a0cd5520ed13460392cf57e8e4e841..a96ab870047c44bd2bb730089054ad1109d891d3 100644 --- a/pkg/bitmask/init.go +++ b/pkg/bitmask/init.go @@ -56,14 +56,18 @@ func initBitmask(printer *message.Printer) (Bitmask, error) { return b, err } -func InitializeBitmask() (Bitmask, error) { +func InitializeBitmask(skipLaunch bool) (Bitmask, error) { + if skipLaunch { + log.Println("Initializing bitmask, but not launching it...") + } if _, err := os.Stat(config.Path); os.IsNotExist(err) { os.MkdirAll(config.Path, os.ModePerm) } err := pid.AcquirePID() if err != nil { - log.Fatal(err) + log.Println("Error acquiring PID:", err) + return nil, err } defer pid.ReleasePID() @@ -75,13 +79,21 @@ func InitializeBitmask() (Bitmask, error) { return nil, err } - err = checkAndStartBitmask(b, conf) + err = setTransport(b, conf) if err != nil { return nil, err } + if !skipLaunch { + err := maybeStartVPN(b, conf) + if err != nil { + log.Println("Error starting VPN: ", err) + return nil, err + } + } + var as Autostart - if conf.DisableAustostart { + if skipLaunch || conf.DisableAustostart { as = &dummyAutostart{} } else { as = newAutostart(config.ApplicationName, "") @@ -103,7 +115,7 @@ func initPrinter() *message.Printer { return message.NewPrinter(message.MatchLanguage(locale, "en")) } -func checkAndStartBitmask(b Bitmask, conf *config.Config) error { +func setTransport(b Bitmask, conf *config.Config) error { if conf.Obfs4 { err := b.UseTransport("obfs4") if err != nil { @@ -111,12 +123,6 @@ func checkAndStartBitmask(b Bitmask, conf *config.Config) error { return err } } - - err := maybeStartVPN(b, conf) - if err != nil { - log.Println("Error starting VPN: ", err) - return err - } return nil } diff --git a/test.pro b/test.pro new file mode 100644 index 0000000000000000000000000000000000000000..099e18fabdddcf1eace328dccd75de3be9a96f67 --- /dev/null +++ b/test.pro @@ -0,0 +1,26 @@ +TEMPLATE = app +TARGET = test_ui +CONFIG += warn_on qmltestcase + +SOURCES += \ + tests/test_ui.cpp \ + gui/qjsonmodel.cpp \ + gui/handlers.cpp + +HEADERS += \ + lib/libgoshim.h \ + gui/qjsonmodel.h \ + gui/handlers.h + + +LIBS += -L../lib -lgoshim -lpthread + +DESTDIR = build +OBJECTS_DIR = build/.obj +RCC_DIR = build/.rcc +UI_DIR = build/.ui + +Release:DESTDIR = build +Release:OBJECTS_DIR = build/.obj +Release:RCC_DIR = build/.rcc +Release:UI_DIR = build/.ui diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..65af933b2fd8663636581c84a5335e71080a93cb --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,10 @@ +build/* +*.h +*.sh +*.moc +*.o +*.stash +Makefile +test_ui +moc_handlers.cpp +moc_qjsonmodel.cpp diff --git a/tests/test_ui.cpp b/tests/test_ui.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f9f960a1717f8ef12d9214a94ab897346575066f --- /dev/null +++ b/tests/test_ui.cpp @@ -0,0 +1,52 @@ +// test_ui.cpp +#include <QtQuickTest> +#include <QQmlEngine> +#include <QQmlContext> + +#include "../gui/qjsonmodel.h" +#include "../lib/libgoshim.h" + +class Helper : public QObject +{ + Q_OBJECT + +public: + explicit Helper(QObject *parent = 0); + +public slots: + Q_INVOKABLE QString refreshContext(); +}; + +Helper::Helper(QObject *parent) : QObject(parent) +{ +} + +Q_INVOKABLE QString Helper::refreshContext() +{ + return QString(RefreshContext()); +} + +class Setup : public QObject +{ + Q_OBJECT + +public: + Setup() {} + +public slots: + void qmlEngineAvailable(QQmlEngine *engine) + { + QQmlContext *ctx = engine->rootContext(); + QJsonModel *model = new QJsonModel; + Helper *helper = new Helper(this); + + InitializeTestBitmaskContext(); + + ctx->setContextProperty("jsonModel", model); + ctx->setContextProperty("helper", helper); + } +}; + +QUICK_TEST_MAIN_WITH_SETUP(ui, Setup) + +#include "test_ui.moc" diff --git a/tests/tst_smoke.qml b/tests/tst_smoke.qml new file mode 100644 index 0000000000000000000000000000000000000000..19904a61420aeb4dfdbb129f75a4749b811035e2 --- /dev/null +++ b/tests/tst_smoke.qml @@ -0,0 +1,28 @@ +import QtQuick 2.3 +import QtTest 1.0 + + +TestCase { + name: "SmokeTests" + + property var ctx + + function refresh() { + ctx = JSON.parse(helper.refreshContext()) + } + + function test_helper() { + compare(Boolean(helper), true, "does helper exist?") + } + + function test_model() { + compare(Boolean(jsonModel), true, "does model exist?") + } + + function test_loadCtx() { + refresh() + compare(ctx.appName, "RiseupVPN", "can read appName?") + compare(ctx.tosURL, "https://riseup.net/tos", "can read tosURL?") + compare(ctx.status, "off", "is initial status off?") + } +}