diff --git a/.gitignore b/.gitignore index d7faae6b6..97f524812 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /deploy/core/node_modules/lighttable/bootstrap.js* .lein-deps-sum .lein-env +.lein-failures .lein-plugins/ .nrepl-port /builds/ diff --git a/.travis.yml b/.travis.yml index fac1fc103..e35221df3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,26 @@ -language: clojure +env: +- TRAVIS=1 +os: + - linux + - osx lein: lein -script: "! lein cljsbuild once 2>&1 | grep WARNING:" -jdk: -- oraclejdk8 +env: + global: + GITHUB_OAUTH_READONLY_TOKEN: 9c40494099f5c56573b6438ff96dfddfb223cbcb +script: + - ./script/build.sh + - ./script/run-tests.sh +language: java +before_script: + - if [[ "${TRAVIS_OS_NAME}" = "linux" ]] ; then export DISPLAY=:99.0; sh -e /etc/init.d/xvfb start; sudo apt-get -qq update; sudo apt-get install -y leiningen; fi + - if [[ "${TRAVIS_OS_NAME}" = "osx" ]] ; then brew update; brew install leiningen; fi notifications: irc: "chat.freenode.net#lighttable" email: - - gabriel.horner@gmail.com - - kenny.evitt@gmail.com - - mrundberget@hotmail.com + - gabriel.horner@gmail.com + - kenny.evitt@gmail.com + - mrundberget@hotmail.com +addons: + apt: + packages: + - chromium-browser diff --git a/README.md b/README.md index a14b96002..d5bb76fc9 100755 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ LightTable is primarily written in [ClojureScript](http://clojure.org/clojurescr In order to develop for Light Table, you will need to [install a developer version of Light Table](doc/developer-install.md). For more information, read [CONTRIBUTING.md](https://github.com/LightTable/LightTable/blob/master/CONTRIBUTING.md#code-contributions) and [For Developers](https://github.com/LightTable/LightTable/wiki/For-Developers). +To execute tests, run ```script/run-tests.sh```. This should be done after running ```script/build.sh```. + ## License diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..e516dc635 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,52 @@ +environment: + global: + CYG_ROOT: C:\cygwin + CYG_MIRROR: http://cygwin.mirror.constant.com + CYG_CACHE: C:\cygwin\var\cache\setup + CYG_BASH: C:\cygwin\bin\bash + GITHUB_OAUTH_READONLY_TOKEN: 9c40494099f5c56573b6438ff96dfddfb223cbcb +cache: + - '%CYG_CACHE%' + +install: + - 'echo Setting up Cygwin dependencies' + - '%CYG_ROOT%\setup-x86.exe -qnNdO -R "%CYG_ROOT%" -s "%CYG_MIRROR%" -l "%CYG_CACHE%" -P autoconf -P automake -P bison -P gcc-core -P gcc-g++ -P mingw-runtime -P mingw-binutils -P mingw-gcc-core -P mingw-gcc-g++ -P mingw-pthreads -P mingw-w32api -P libtool -P make -P python -P gettext-devel -P gettext -P intltool -P libiconv -P pkg-config -P git -P wget -P curl -P unzip -P psmisc> NUL' + + - 'echo Check Cygwin setup' + - '%CYG_ROOT%/bin/bash -lc "cygcheck -dc cygwin"' + - 'echo Done setting up Cygwin' + + - 'echo installing lein' + - ps: | + $base = "https://raw.githubusercontent.com/technomancy/leiningen/" + $lein = $base + "stable/bin/lein.bat" + (new-object net.webclient).DownloadFile($lein, "c:/projects/lighttable/lein.bat") + - lein self-install + - 'echo Done installing lein' + #Add random location, this prevents qutation mark being added to + #the current directory's path + - cmd: 'set PATH="%PATH%;%cd%;c:\windows\system32"' + - cmd: 'echo %PATH%' + +build_script: + - cmd: 'echo Cygwin root is: %CYG_ROOT%' + - cmd: 'echo Build folder is: %APPVEYOR_BUILD_FOLDER%' + - cmd: 'echo Repo build branch is: %APPVEYOR_REPO_BRANCH%' + - cmd: 'echo Repo build commit is: %APPVEYOR_REPO_COMMIT%' + + - cmd: 'echo creating lein reference' + - '%CYG_ROOT%/bin/bash -lc "cd $APPVEYOR_BUILD_FOLDER; echo \"#!/bin/bash\" > lein.sh"' + - '%CYG_ROOT%/bin/bash -lc "cd $APPVEYOR_BUILD_FOLDER; echo \"./lein.bat \$@\" >> lein.sh"' + - '%CYG_ROOT%/bin/bash -lc "cd $APPVEYOR_BUILD_FOLDER; chmod a+x lein.sh"' + - '%CYG_ROOT%/bin/bash -lc "cd $APPVEYOR_BUILD_FOLDER; ln -s /cygdrive/c/projects/lighttable/lein.sh lein; ls -la"' + + - cmd: 'echo Running build.sh' + - '%CYG_ROOT%/bin/bash -lc "cd $APPVEYOR_BUILD_FOLDER; ./script/build.sh"' + + - '%CYG_ROOT%/bin/bash -lc "cd $APPVEYOR_BUILD_FOLDER; ./script/run-tests.sh"' + #DEBUG BEGIN, PAUSES BUILD PROCESS TO ALLOW FOR REMOTE DEBUGGING VIA RDP + #- ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) + #- ps: throw "Failing tests" + #DBEUG END + + diff --git a/deploy/core/main.js b/deploy/core/main.js index b05b0997f..129a7188e 100644 --- a/deploy/core/main.js +++ b/deploy/core/main.js @@ -57,11 +57,6 @@ function createWindow() { evt.preventDefault(); }); - // Emitted when the window is closed. - window.on('closed', function() { - windows[window.id] = null; - }); - return window; }; diff --git a/deploy/electron/Gruntfile.js b/deploy/electron/Gruntfile.js index c736e21d9..d0bf68136 100644 --- a/deploy/electron/Gruntfile.js +++ b/deploy/electron/Gruntfile.js @@ -6,7 +6,8 @@ module.exports = function(grunt) { "download-electron": { version: "0.36.7", outputDir: "./electron", - rebuild: true + rebuild: true, + token: process.env.GITHUB_OAUTH_READONLY_TOKEN } }); diff --git a/doc/testing.md b/doc/testing.md new file mode 100644 index 000000000..44d178e75 --- /dev/null +++ b/doc/testing.md @@ -0,0 +1,28 @@ +# TESTING + +## Quick Guide +After running ```/script/build.sh``` once, run ```/script/run-tests.sh```. + +##Continuous Testing Architecture Overview +When a PR is created or updated, the automated test suite will run on travis and appveyor. + +During the build processes, dependencies must be downloaded from github. API +rate limits were being hit. To increase the limit, a read-only oauth token for +the account ```light-table-bot``` has been added to the repo. + +Light Table is driven by Selenium for testing. + +Note a "test" plugin has been added to Light Table, found in `test/lt/lt-test-plugin/`. +The plugin replaces the native "file dialog" so files can be selected and modified. +During testing, the plugin is copied into a temporary home directory for Light Table, forcing +it to load. + + +## Words Of Caution +Keep in mind the jvm is slow to boot, and the testing environments used may +be under heavy load. If you need to have a process spawn close to the launching +of Light Table, launch the program in the java tests to minimize delay. + +Initially ```light.sh``` was used by test suite to start Light Table. Kill signals +were not propigating correctly and Light Table would not always close. Thus, Light Table +is spawned directly from the test suite. diff --git a/project.clj b/project.clj index 10f190a41..bc743f39f 100644 --- a/project.clj +++ b/project.clj @@ -21,12 +21,17 @@ :compiler {:optimizations :simple :output-to "deploy/core/node_modules/clojurescript/cljsDeps.js" :output-dir "deploy/core/node_modules/clojurescript/cljsDeps/" - :pretty-print true }}]} + :pretty-print true }} ]} ;; TODO: Remove separate :doc :dependencies after ClojureScript upgrade :profiles {:doc {:dependencies [[org.clojure/clojure "1.7.0"] [org.clojure/clojurescript "1.7.145" - :exclusions [org.apache.ant/ant]]]}} + :exclusions [org.apache.ant/ant]] ]} + :dev {:dependencies [[clj-webdriver "0.7.2"] + [org.seleniumhq.selenium/selenium-java "2.47.0"] + [me.raynes/fs "1.4.6"]] + :plugins [[test2junit "1.1.2"] + [lein-cljsbuild "1.0.1"]]}} :plugins [[lein-cljsbuild "1.0.1"] [lein-codox "0.9.5"]] :codox {:language :clojurescript @@ -37,7 +42,5 @@ lt.objs.editor.pool lt.objs.files lt.objs.notifos] :source-uri "https://github.com/LightTable/LightTable/blob/{version}/{filepath}#L{line}" ;; Be explicit that undocumented public fns should be documented - :metadata {:doc "TODO: Add docstring" - :doc/format :markdown}} - :source-paths ["src/"] - ) + :metadata {:doc "TODO: Add docstring"}} + :source-paths ["src/"]) diff --git a/script/common.sh b/script/common.sh new file mode 100644 index 000000000..54ba53754 --- /dev/null +++ b/script/common.sh @@ -0,0 +1,27 @@ +# Ensure we start in project root +cd "$(dirname "${BASH_SOURCE[0]}")"; cd .. +DIR=$(pwd) + +ISWINDOWS=0 +if [ "$(uname)" == "Darwin" ]; then + CLI="${DIR}/deploy/electron/electron/Electron.app/Contents/MacOS/Electron" +elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then + CLI="${DIR}/deploy/electron/electron/electron" +elif [ "$(expr substr $(uname -s) 1 9)" == "CYGWIN_NT" ]; then + ISWINDOWS=1 + CLI="${DIR}/deploy/electron/electron/electron.exe" +else + echo "Cannot detect a supported OS." + exit 1 +fi + +#Converts unix path to windows path if necessary +nativepath() +{ + if [ "$ISWINDOWS" -eq "1" ] + then + echo $(cygpath -w "$1") + else + echo $1 + fi +} diff --git a/script/run-tests.sh b/script/run-tests.sh new file mode 100755 index 000000000..13569e794 --- /dev/null +++ b/script/run-tests.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# Installs test dependencies, sets up environment variables, +# calls lein test, and then parses test results to see if +# the tests succeeded. + +# Ensure we start in project root +cd "$(dirname "${BASH_SOURCE[0]}")"; cd .. +DIR=$(pwd) + +if + +#Redirect LightTable STDOUT to a file to clean up test output +LT_STDOUT_FILE="/tmp/lt-stdout" +touch $LT_STDOUT_FILE +if [[ $(uname -s | grep -i CYGWIN) ]]; then + LT_STDOUT_FILE="$(cygpath -aw $LT_STDOUT_FILE)" +fi + +echo "Killing all chromedrivers. New instance will be created by test suite." +killall "chromedriver" &> /dev/null +killall "chromedriver.exe" &> /dev/null + +#Fetch chrome driver +cd ./deploy/electron/ +node_modules/.bin/grunt download-electron-chromedriver +cd - + +echo "Running tests" +LT_STDOUT_FILE="$LT_STDOUT_FILE" lein with-profile dev test | tee /tmp/testoutput + +#Check test results +#lein.bat return code is 0 even if tests fails on windows +#we grep the lein test output to to make sure no tests fail or errors +#this gives us cross os compatibility +tail -n 4 /tmp/testoutput | grep "^0 failures, 0 errors" > /dev/null +RETURN_CODE=$? + +echo "===LIGHT TABLE DEBUG===" +cat $LT_STDOUT_FILE + +exit $RETURN_CODE diff --git a/src/lt/objs/dialogs.cljs b/src/lt/objs/dialogs.cljs index 82b7df880..9a298b361 100644 --- a/src/lt/objs/dialogs.cljs +++ b/src/lt/objs/dialogs.cljs @@ -8,16 +8,35 @@ (def remote (js/require "remote")) (def dialog (.require remote "dialog")) -(defn dir [obj event] - (let [files (.showOpenDialog dialog app/win #js {:properties #js ["openDirectory" "multiSelections"]})] +(defn set-show-file-dialog-fn! [f] (def show-file-dialog f)) + +(set-show-file-dialog-fn! + (fn [type options callback] + (if (= :open type) + (callback (.showOpenDialog dialog app/win options)) + (callback [(.showSaveDialog dialog app/win options)])))) + + +(defn broadcast-file-selected [obj event files] (doseq [file files] - (object/raise obj event file)))) + (if file (object/raise obj event file)))) + +(defn dir [obj event] + (show-file-dialog + :open + #js {:properties #js ["openDirectory" "multiSelections"]} + (partial broadcast-file-selected obj event))) (defn file [obj event] - (let [files (.showOpenDialog dialog app/win #js {:properties #js ["openFile" "multiSelections"]})] - (doseq [file files] - (object/raise obj event file)))) + (show-file-dialog + :open + #js {:properties #js ["openFile" "multiSelections"]} + (partial broadcast-file-selected obj event))) (defn save-as [obj event path] - (when-let [file (.showSaveDialog dialog app/win #js {:defaultPath path})] - (object/raise obj event file))) + (show-file-dialog + :save + #js {:defaultPath path} + (partial broadcast-file-selected obj event))) + + diff --git a/test/lt/lt-test-plugin/README.md b/test/lt/lt-test-plugin/README.md new file mode 100644 index 000000000..d9b5a8c91 --- /dev/null +++ b/test/lt/lt-test-plugin/README.md @@ -0,0 +1,14 @@ +Mocks LightTable components so application can be tested. + +To compile and update plugin: + +1. Create symbolic link in LightTable plugin directory to plugin source + + ~~~ + ln -s ~/programming/LightTable/test/lt/lt-test-plugin ~/Library/Application\ Support/LightTable/plugins/lighttable-test-plugin + ~~~ +1. Open plugin sourcecode in light table , `src/lt/plugins/lighttable-test-plugin.cljs`. +1. Save cljs file. Light table should auto compile. +1. Remove symbolic link created in step one. +1. Run `lein test` to ensure tests still work. + diff --git a/test/lt/lt-test-plugin/lighttable test plugin_compiled.js b/test/lt/lt-test-plugin/lighttable test plugin_compiled.js new file mode 100644 index 000000000..c9768237c --- /dev/null +++ b/test/lt/lt-test-plugin/lighttable test plugin_compiled.js @@ -0,0 +1,28 @@ +if(!lt.util.load.provided_QMARK_('lt.plugins.lighttable-test-plugin')) { +goog.provide('lt.plugins.lighttable_test_plugin'); +goog.require('cljs.core'); +goog.require('lt.objs.command'); +goog.require('lt.objs.command'); +goog.require('lt.util.dom'); +goog.require('lt.util.dom'); +goog.require('lt.objs.popup'); +goog.require('lt.objs.popup'); +goog.require('clojure.string'); +goog.require('clojure.string'); +lt.plugins.lighttable_test_plugin.debug_file_path_input = cljs.core.atom.call(null,""); +/** +* Mocks show-file-dialog so native file dialog does not show. +* A popup dialog with a text field is shown instead. File paths +* can be specified in the path fields, seperated by commas to donate +* multiple files. +*/ +lt.plugins.lighttable_test_plugin.show_file_dialog_mock = (function show_file_dialog_mock(type,options,callback){return lt.objs.popup.popup_BANG_.call(null,new cljs.core.PersistentArrayMap(null, 3, [new cljs.core.Keyword(null,"id","id",1013907597),"test2",new cljs.core.Keyword(null,"body","body",1016933652),new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"div","div",1014003715),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"h1","h1",1013907515),"File Dialog Mock"], null),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"input","input",1114262332),new cljs.core.PersistentArrayMap(null, 2, [new cljs.core.Keyword(null,"type","type",1017479852),"text",new cljs.core.Keyword(null,"id","id",1013907597),"debug-file-path"], null)], null)], null),new cljs.core.Keyword(null,"buttons","buttons",1255256819),new cljs.core.PersistentVector(null, 1, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.PersistentArrayMap(null, 4, [new cljs.core.Keyword(null,"id","id",1013907597),"test",new cljs.core.Keyword(null,"label","label",1116631654),"Okay",new cljs.core.Keyword(null,"action","action",3885920680),(function (){return cljs.core.swap_BANG_.call(null,lt.plugins.lighttable_test_plugin.debug_file_path_input,(function (_){return lt.util.dom.val.call(null,lt.util.dom.$.call(null,"#debug-file-path")); +})); +}),new cljs.core.Keyword(null,"post-action","post-action",4039331701),(function (){return callback.call(null,clojure.string.split.call(null,cljs.core.deref.call(null,lt.plugins.lighttable_test_plugin.debug_file_path_input),/,/)); +})], null)], null)], null)); +}); +lt.objs.command.command.call(null,new cljs.core.PersistentArrayMap(null, 3, [new cljs.core.Keyword(null,"command","command",1964298941),new cljs.core.Keyword(null,"lt.testing.mock","lt.testing.mock",2290833600),new cljs.core.Keyword(null,"desc","desc",1016984067),"Test Env: Mocks light table components for automated tests.",new cljs.core.Keyword(null,"exec","exec",1017031683),(function (){return lt.objs.dialogs.set_show_file_dialog_fn_BANG_.call(null,lt.plugins.lighttable_test_plugin.show_file_dialog_mock).call(null); +})], null)); +} + +//# sourceMappingURL=lighttable test plugin_compiled.js.map \ No newline at end of file diff --git a/test/lt/lt-test-plugin/lighttable test plugin_compiled.js.map b/test/lt/lt-test-plugin/lighttable test plugin_compiled.js.map new file mode 100644 index 000000000..6eb48e9eb --- /dev/null +++ b/test/lt/lt-test-plugin/lighttable test plugin_compiled.js.map @@ -0,0 +1,16 @@ +{"version":3, + "file": + "compiled.map", + "sources": + ["src/lt/plugins/lighttable-test-plugin.cljs"], + "lineCount":26, + "mappings": + ";AAAA;;;;;;;;;;AAOA,AAAKA,0DAAsB,yBAAA,zBAACC;AAE5B;;;;;;0DAAA,uDAAA,jHAAMC,0FAKHC,KAAKC,QAAQC,UACd,2CAAA,2CAAA,iDAAA,QAAA,qDAAA,mFAAA,mDAAA,mFAAA,iDAAA,2BAAA,mFAAA,uDAAA,2CAAA,qDAAA,OAAA,iDAAA,0CAAA,2DAAA,mFAAA,2CAAA,iDAAA,OAAA,uDAAA,OAAA,yDAAA,aAAA,nqCAACC,mqCAMyB,OAACC,+BAAMP,wDAAsB,cAAA,HAAKQ,GAAG,OAACC,0BAAQ,wBAAA,xBAACC;;GANzE,mEAAA,aAAA,AAO+B,OAACL,mBAAS,+BAAA,AAAAM,mFAAA,lHAACC,yDAAcZ;;;AAK1D,kCAAA,2CAAA,2DAAA,2EAAA,qDAAA,8DAAA,qDAAA,aAAA,xYAACa,wYAEoB,OAEG,AAACC,wDAAyCZ", + "names": + ["lt.plugins.lighttable-test-plugin/debug-file-path-input", + "cljs.core/atom", + "lt.plugins.lighttable-test-plugin/show-file-dialog-mock", "type", + "options", "callback", "lt.objs/popup/popup!", "cljs.core/swap!", + "_", "lt.util.dom/val", "lt.util.dom/$", "cljs.core/deref", + "clojure.string/split", "lt.objs.command/command", + "lt.objs.dialogs/set-show-file-dialog-fn!"]} \ No newline at end of file diff --git a/test/lt/lt-test-plugin/lighttable-test-plugin.behaviors b/test/lt/lt-test-plugin/lighttable-test-plugin.behaviors new file mode 100644 index 000000000..10866ef63 --- /dev/null +++ b/test/lt/lt-test-plugin/lighttable-test-plugin.behaviors @@ -0,0 +1 @@ +{:+ {:app [(:lt.objs.plugins/load-js ["lighttable test plugin_compiled.js"])]}} diff --git a/test/lt/lt-test-plugin/plugin.edn b/test/lt/lt-test-plugin/plugin.edn new file mode 100644 index 000000000..3c0f2ed84 --- /dev/null +++ b/test/lt/lt-test-plugin/plugin.edn @@ -0,0 +1,6 @@ +{:name "LightTable Testing Plugin" + :author "Justin Taft" + :source "https://github.com/LightTable/LightTable" + :desc "Mocks Light Table components so application can be tested." + :behaviors "lighttable-test-plugin.behaviors"} + diff --git a/test/lt/lt-test-plugin/plugin.json b/test/lt/lt-test-plugin/plugin.json new file mode 100644 index 000000000..3d2b8e270 --- /dev/null +++ b/test/lt/lt-test-plugin/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "LightTable Test Plugin", + "author": "Justin Taft", + "version": "0.0.1", + "source": "https://github.com/LightTable/LightTable", + "desc": "Mocks LightTable components so they can be tested", + "behaviors": "lighttable-test-plugin.behaviors" +} diff --git a/test/lt/lt-test-plugin/project.clj b/test/lt/lt-test-plugin/project.clj new file mode 100644 index 000000000..38b3038d1 --- /dev/null +++ b/test/lt/lt-test-plugin/project.clj @@ -0,0 +1,2 @@ +(defproject lighttable-test-plugin "0.0.1" + :dependencies [[org.clojure/clojure "1.5.1"]]) diff --git a/test/lt/lt-test-plugin/src/lt/plugins/lighttable-test-plugin.cljs b/test/lt/lt-test-plugin/src/lt/plugins/lighttable-test-plugin.cljs new file mode 100644 index 000000000..68e5ee74a --- /dev/null +++ b/test/lt/lt-test-plugin/src/lt/plugins/lighttable-test-plugin.cljs @@ -0,0 +1,34 @@ +(ns lt.plugins.lighttable-test-plugin + "Mocks & extends light table components for auotmated testing." + (:require [clojure.string :as string] + [lt.objs/popup :as popup] + [lt.util.dom :as dom] + [lt.objs.command :as cmd])) + +(def debug-file-path-input (atom "")) + +(defn show-file-dialog-mock + "Mocks show-file-dialog so native file dialog does not show. + A popup dialog with a text field is shown instead. File paths + can be specified in the path fields, seperated by commas to donate + multiple files." + [type options callback] + (popup/popup! {:body [:div [:h1 "File Dialog Mock"] [:input {:type "text" :id "debug-file-path"}]] + :buttons + [ + { + :label "Okay" + :action #(swap! debug-file-path-input (fn [_] (dom/val (dom/$ "#debug-file-path")))) + :post-action #(callback (string/split @debug-file-path-input #",")) + } + ] + })) + +(cmd/command {:command :lt.testing.mock + :desc "Test Env: Mocks light table components for automated tests." + :exec #( + ;TODO error is thrown when evaling this, but it works... + (lt.objs.dialogs/set-show-file-dialog-fn! show-file-dialog-mock) + ) + }) + diff --git a/test/lt/lt-test-plugin/target/classes/META-INF/maven/lighttable-test-plugin/lighttable-test-plugin/pom.properties b/test/lt/lt-test-plugin/target/classes/META-INF/maven/lighttable-test-plugin/lighttable-test-plugin/pom.properties new file mode 100644 index 000000000..88ebf2738 --- /dev/null +++ b/test/lt/lt-test-plugin/target/classes/META-INF/maven/lighttable-test-plugin/lighttable-test-plugin/pom.properties @@ -0,0 +1,5 @@ +#Leiningen +#Sat Nov 12 01:47:39 PST 2016 +version=0.0.1 +groupId=lighttable-test-plugin +artifactId=lighttable-test-plugin diff --git a/test/lt/lt-test-plugin/target/classes/META-INF/maven/lt-test-file-dialog-mock/lt-test-file-dialog-mock/pom.properties b/test/lt/lt-test-plugin/target/classes/META-INF/maven/lt-test-file-dialog-mock/lt-test-file-dialog-mock/pom.properties new file mode 100644 index 000000000..2ad9b411e --- /dev/null +++ b/test/lt/lt-test-plugin/target/classes/META-INF/maven/lt-test-file-dialog-mock/lt-test-file-dialog-mock/pom.properties @@ -0,0 +1,5 @@ +#Leiningen +#Sat Nov 12 00:56:46 PST 2016 +version=0.0.1 +groupId=lt-test-file-dialog-mock +artifactId=lt-test-file-dialog-mock diff --git a/test/lt/lt-test-plugin/target/classes/META-INF/maven/lt-test-plugin/lt-test-plugin/pom.properties b/test/lt/lt-test-plugin/target/classes/META-INF/maven/lt-test-plugin/lt-test-plugin/pom.properties new file mode 100644 index 000000000..f73348c87 --- /dev/null +++ b/test/lt/lt-test-plugin/target/classes/META-INF/maven/lt-test-plugin/lt-test-plugin/pom.properties @@ -0,0 +1,5 @@ +#Leiningen +#Sat Nov 12 01:07:36 PST 2016 +version=0.0.1 +groupId=lt-test-plugin +artifactId=lt-test-plugin diff --git a/test/lt/lt-test-plugin/target/repl-port b/test/lt/lt-test-plugin/target/repl-port new file mode 100644 index 000000000..17a26e70b --- /dev/null +++ b/test/lt/lt-test-plugin/target/repl-port @@ -0,0 +1 @@ +60996 \ No newline at end of file diff --git a/test/lt/lt-test-plugin/target/stale/extract-native.dependencies b/test/lt/lt-test-plugin/target/stale/extract-native.dependencies new file mode 100644 index 000000000..e731ff5a6 --- /dev/null +++ b/test/lt/lt-test-plugin/target/stale/extract-native.dependencies @@ -0,0 +1 @@ +([:dependencies ([org.clojure/clojure "1.5.1"] [org.clojure/tools.nrepl "0.2.10" :exclusions ([org.clojure/clojure])] [clojure-complete/clojure-complete "0.2.3" :exclusions ([org.clojure/clojure])] [lein-light-nrepl/lein-light-nrepl "0.1.3"] [lein-light-nrepl-instarepl/lein-light-nrepl-instarepl "0.3.1"])]) \ No newline at end of file diff --git a/test/lt/lt-test-plugin/target/stale/leiningen.core.classpath.extract-native-dependencies b/test/lt/lt-test-plugin/target/stale/leiningen.core.classpath.extract-native-dependencies new file mode 100644 index 000000000..ee9db9358 --- /dev/null +++ b/test/lt/lt-test-plugin/target/stale/leiningen.core.classpath.extract-native-dependencies @@ -0,0 +1 @@ +[{:dependencies {org.clojure/clojure {:vsn "1.5.1", :native-prefix nil}, org.clojure/tools.nrepl {:vsn "0.2.12", :native-prefix nil}, clojure-complete {:vsn "0.2.4", :native-prefix nil}}, :native-path "target/native"} {:native-path "target/native", :dependencies {org.clojure/clojure {:vsn "1.5.1", :native-prefix nil, :native? false}, org.clojure/tools.nrepl {:vsn "0.2.12", :native-prefix nil, :native? false}, clojure-complete {:vsn "0.2.4", :native-prefix nil, :native? false}}}] \ No newline at end of file diff --git a/test/lt/system_tests.clj b/test/lt/system_tests.clj new file mode 100644 index 000000000..024328571 --- /dev/null +++ b/test/lt/system_tests.clj @@ -0,0 +1,144 @@ +(ns lt.system-tests + (:import + (org.openqa.selenium Keys) + (java.io File) + (java.nio.file Path) + (java.nio.file Paths) + (java.nio.file Files) + (java.nio.file.attribute FileAttribute) + (java.nio.file OpenOption) + (java.io File)) + (:require + [lt.webdriver-helper :as lt-webdriver-helper] + [clj-webdriver.taxi :as taxi] + [clj-webdriver.core :as core] + [clj-webdriver.element :as element] + [clojure.string :as str] + [clojure.test :refer :all :as test])) + + +(defn create-temporary-file [] + "Creates a temporary file. Caller is responsible for removing file." +(-> (Files/createTempFile (String. "temp-file-name") (String. ".tmp") (into-array FileAttribute [])) + (.toAbsolutePath) + (.toString))) + + +(defn read-file-contents [path] + "Fetches all contents of a file located at path." + (-> (Paths/get path (into-array String [])) + (Files/readAllBytes) + (String. ))) + + +(defn wait-until-true + "Repeatedly calls function until function produces non-null value within max-time. + Throws Exception if function does not produce a non-false value within max-time." + ([max-time fn] + (let [future-time (+ (System/currentTimeMillis) max-time)] + (loop [return-value (fn)] + (if return-value + return-value + (if (<= future-time (System/currentTimeMillis)) + (throw (Exception. "Wait-until-true never passed.")) + (do + (Thread/sleep 100) + (recur (fn)))))))) + ([fn] (wait-until-true 1000 fn))) + + + + + +(defn helper-open-command-list [] + "Opens command list window (ctrl+space)." + (taxi/send-keys (taxi/find-element {:css "body"}) (Keys/chord [ (core/key-code :control) (core/key-code :space)])) + (wait-until-true #(taxi/find-element {:css ".search"}))) + +(defn helper-select-command-list-command [name] + (-> (taxi/switch-to-active) + (.sendKeys (into-array CharSequence (list name (core/key-code :enter)))))) + +(defn helper-select-files [files] + (-> (wait-until-true #(taxi/find-element {:css "#debug-file-path"} )) + (taxi/send-keys (str (first files)))) + ;TODO make more deterministic to make sure we are clicking "Okay" + (taxi/click {:css ".button.active"})) + +(defn get-file-name-from-path [path] + (-> (File. path) + (.getName))) + +(defn lt-fixture [func] + (try + (lt-webdriver-helper/start-chromedriver!) + (lt-webdriver-helper/start-light-table!) + + ;Nasty hack. mitigates possibility of running into bug + ;in older version of electron where packages (atom.asar) + ;is freed when using chromedriver. + (Thread/sleep 2000) + (lt-webdriver-helper/connect-to-light-table!) + + (func) + (finally + (lt-webdriver-helper/stop-chromedriver!) + (lt-webdriver-helper/stop-light-table!)))) + +(use-fixtures :each lt-fixture) + +(deftest new-file + + ;TODO figure out a better way to determine if program finished booting. + ;Current checking for the position element, showing line numbers in the bottom + ;right of the screen. + (wait-until-true 40000 #(taxi/find-element {:tag :span :class "pos"})) + + ;Create temporary file + (def temp-file-path (create-temporary-file)) + + ;Enable test mocks for testing + (helper-open-command-list) + (helper-select-command-list-command "Test Env:") + + ;Create new file + (helper-open-command-list) + (helper-select-command-list-command "new file") + + ;Wait for editor to appear + (wait-until-true #(taxi/find-element {:css "div.codemirror-focused"})) + + ;Type some content + (-> (element/init-element (taxi/switch-to-active)) + (taxi/send-keys "File Persistence Test")) + + + ;Save file + (helper-open-command-list) + (helper-select-command-list-command "Save File") + (helper-select-files [temp-file-path]) + + ;Verify file contents. Waits up to 2 seconds before + ;determining content failed to save. + + (is (wait-until-true 2000 #(= "File Persistence Test" (read-file-contents temp-file-path)))) + + ;Close Tab + (core/->actions (lt-webdriver-helper/get-webdriver) + (core/move-to-element + (taxi/find-element {:tag :span :class "file-name" :text (get-file-name-from-path temp-file-path) }))) + + (taxi/click {:css ".tabset.active .list li.active .tab-close"}) + + ;Open file + (helper-open-command-list) + (helper-select-command-list-command "Open File") + (helper-select-files [temp-file-path]) + + ;assert editor has correct text + (is (wait-until-true #(= "File Persistence Test" (taxi/text {:css ".CodeMirror-code span"})))) + + ;Close Tab + (core/move-to-element (lt-webdriver-helper/get-webdriver) (wait-until-true #(taxi/find-element {:tag :span :class "file-name" :text (get-file-name-from-path temp-file-path) }))) + (taxi/click {:css ".tabset.active .list li.active .tab-close"}) +) diff --git a/test/lt/webdriver_helper.clj b/test/lt/webdriver_helper.clj new file mode 100644 index 000000000..7d292c9b3 --- /dev/null +++ b/test/lt/webdriver_helper.clj @@ -0,0 +1,121 @@ +(ns lt.webdriver-helper + (:import + (java.net URL) + (java.io File) + (org.openqa.selenium.remote DesiredCapabilities) + (org.openqa.selenium.remote RemoteWebDriver) + (org.openqa.selenium.chrome ChromeOptions) + (java.lang Runtime) + (java.nio.file Files) + (java.nio.file.attribute FileAttribute)) + (:use [clojure.string :only [trim]]) + (:require + [clj-webdriver.taxi :as taxi] + [clojure.java.shell :as shell :only [sh]] + [clj-webdriver.driver :as driver] + [me.raynes.fs] + )) + + +(def lein-project-dir (System/getProperty "user.dir")) + +(def chromeOptions { + "debuggerAddress" "127.0.0.1:8315" +}) + +(def capabilities (DesiredCapabilities/chrome)) +(.setCapability capabilities ChromeOptions/CAPABILITY chromeOptions) +(.setCapability capabilities "browserName" "electron") + +(defn log [msg] + (println msg)) + +(defn get-project-directory [] + (System/getProperty "user.dir")) + + +(def lt-process (atom nil)) +(def chromedriver-process (atom nil)) + +;Defines temporary directory for testing light table +(def temp-home-directory + (str (Files/createTempDirectory "lightable_homedir_for_testing" (into-array FileAttribute [])))) + +(defn get-chromedriver-path [] + "Finds path to chromedriver/" + (str (first (filter #(re-find #"chromedriver" (.getName %1)) + (-> (File. (str (System/getProperty "user.dir") "/deploy/electron/electron/chromedriver/")) + .listFiles))))) + + +(defn get-lighttable-executable-path [] + "Finds path to LightTable" + (first (filter + #(.exists (File. %1)) + (map #(str (System/getProperty "user.dir") %1) + ["/deploy/electron/electron/Electron.app/Contents/MacOS/Electron" + "/deploy/electron/electron/electron" + "/deploy/electron/electron/electron.exe"])))) + +(defn copy-test-plugins-into-home-directory! [] + "Copies LightTable test enivonrment setup plugin into + temporary home directory." + (me.raynes.fs/copy-dir + (str lein-project-dir "/test/lt/lt-test-plugin") + (str temp-home-directory "/plugins/lt-test-plugin"))) + +(defn create-process-builder-for-lt-launching [temp-home-directory] + "Configures process builder to launch LightTable with testing environment variables." + (let [pb (ProcessBuilder. + (into-array String + [ + (get-lighttable-executable-path) + "deploy/core" + ] + )) + env (.environment pb ) + ] + (.redirectErrorStream pb true) + (when-let [stdout-redirect-file-path (System/getenv "LT_STDOUT_FILE")] + (.redirectOutput pb (File. stdout-redirect-file-path))) + (.put env "LT_USER_DIR" (.toString temp-home-directory)) + (.put env "LT_DEV_CLI" "true") + pb)) +(defn start-light-table! [] + (copy-test-plugins-into-home-directory!) + (swap! lt-process + (fn [_] + (-> (create-process-builder-for-lt-launching temp-home-directory) + (.start)) +))) + +(defn start-chromedriver! [] + "Start chromedriver process." + (swap! chromedriver-process + (fn [_] + (-> (ProcessBuilder. (into-array String [ + (get-chromedriver-path) ])) + (.start))))) + +(defn stop-chromedriver! [] + "Kills chromedriver process." + (when-let [cd @chromedriver-process] + (.destroy cd)) + (swap! chromedriver-process (fn [p] nil))) + + +;TODO check if we are already connected to light-table to avoid re-connecting? +(defn connect-to-light-table! [] + "Connects to light table." + (-> (RemoteWebDriver. (URL. "http://127.0.0.1:9515") capabilities) + (driver/init-driver) + (taxi/set-driver!))) + +(defn stop-light-table! [] + "Kills light table process" + (and @lt-process (.destroy @lt-process)) + (swap! lt-process (fn [p] nil))) + +(defn get-webdriver [] + taxi/*driver*) +