diff --git a/.gitignore b/.gitignore index 271a241..a00bb71 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ g3upt/ g3doc/ out/ +obj/ diff --git a/README.md b/README.md index a6e06fb..8e7e4eb 100755 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Support for Windows may be provided in future, but is not a current priority. * [perl](https://www.perl.org/) ### Guide -`purr` includes a simple tool to help select the device serial from `adb devices`, or can read from the `$ANDROID_SERIAL` environment variable if set. Otherwise, `purr` has six command-line parameters: +`purr` includes a simple tool to help select the device serial from `adb devices`, or can read from the `$ANDROID_SERIAL` environment variable if set. Otherwise, `purr` has the following command-line parameters: * -a: Sets custom parameters for `adb` that will be used as well as the defaults whenever an input stream is selected. * -f: Sets custom parameters for `fzf`. Used on top of default parameters. @@ -52,6 +52,12 @@ Support for Windows may be provided in future, but is not a current priority. * -v: Shows the `purr` version. * -V: Shows a composite version of `purr` and dependencies. +There are also the following special command line parameters that should only be used for testing and debugging: + +* -A: Replace all calls to `adb` with the given binary. This can be used in conjunction with the bundled `adb_mock` binary to perform basic testing on purr. +* -D: Use the given directory to store all generated files instead of the standard /tmp dir. +* -X: Do not execute `fzf` when reached in the execution loop; return as if `fzf` executed correctly. + Any other command-line parameters will print the help dialog. Note that both `-a` and `-f` are read without validation; there is no guarantee that setting either parameter will not break `purr`. diff --git a/makefile b/makefile index 6b4b652..99f5c8b 100644 --- a/makefile +++ b/makefile @@ -1,75 +1,80 @@ OUTDIR?=$(CURDIR)/out +OBJDIR?=$(CURDIR)/obj SRCDIR?=$(CURDIR)/src RESDIR?=$(CURDIR)/res TESTDIR?=$(CURDIR)/tests PURRFILE ?=$(OUTDIR)/purr -PURRFILE_TEMP ?=$(OUTDIR)/purr_temp +PURRFILE_TEMP ?=$(OBJDIR)/purr ADBMOCKFILE ?=$(OUTDIR)/adb_mock +ADBMOCKFILE_TEMP ?=$(OBJDIR)/adb_mock -all: purr adb_mock +FILETESTERFILE ?=$(OUTDIR)/file_tester +FILETESTERFILE_TEMP ?=$(OBJDIR)/file_tester + +all: purr adb_mock file_tester .PHONY: purr purr: mkdir -p $(OUTDIR) + mkdir -p $(OBJDIR) + echo "" > $(PURRFILE) + echo "" > $(PURRFILE_TEMP) # We first need the threading functions, since we use them as part of shell # env setup, specifically to create the cache files. - cat $(SRCDIR)/threads/thread_background.sh >> "$(PURRFILE)" - cat $(SRCDIR)/threads/thread_controller.sh >> "$(PURRFILE)" - cat $(SRCDIR)/threads/thread_interface.sh >> "$(PURRFILE)" - cat $(SRCDIR)/threads/thread_setup.sh >> "$(PURRFILE)" + cat $(SRCDIR)/threads/thread_background.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/threads/thread_controller.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/threads/thread_interface.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/threads/thread_setup.sh >> "$(PURRFILE_TEMP)" # We can then cleanly do shell setup. - cat $(SRCDIR)/shell/shell_utils.sh >> "$(PURRFILE)" - cat $(SRCDIR)/shell/argument_parsing.sh >> "$(PURRFILE)" - cat $(SRCDIR)/shell/shell_env_check.sh >> "$(PURRFILE)" - cat $(SRCDIR)/shell/shell_env_init.sh >> "$(PURRFILE)" + cat $(SRCDIR)/shell/shell_utils.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/shell/argument_parsing.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/shell/shell_env_check.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/shell/shell_env_init.sh >> "$(PURRFILE_TEMP)" # After we've validated the shell, we want to query for a serial number. - cat $(SRCDIR)/serial/serial_picker.sh >> "$(PURRFILE)" - cat $(SRCDIR)/serial/serial_interface.sh >> "$(PURRFILE)" - cat $(SRCDIR)/serial/serial_init.sh >> "$(PURRFILE)" + cat $(SRCDIR)/serial/serial_picker.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/serial/serial_interface.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/serial/serial_init.sh >> "$(PURRFILE_TEMP)" # Load in all the UI utilities. We'll need these for setting up the fzf # environment in the next step. - cat $(SRCDIR)/ui/ui_strings.sh >> "$(PURRFILE)" - cat $(SRCDIR)/ui/ui_utils.sh >> "$(PURRFILE)" + cat $(SRCDIR)/ui/ui_strings.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/ui/ui_utils.sh >> "$(PURRFILE_TEMP)" # Finalizes the fzf environment; program is in a ready state at this point. # We just need to load in all the UI/UX elements from bindings. - cat $(SRCDIR)/fzf_env/load_copy_program.sh >> "$(PURRFILE)" - cat $(SRCDIR)/fzf_env/fzf_default_state.sh >> "$(PURRFILE)" + cat $(SRCDIR)/fzf_env/load_copy_program.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/fzf_env/fzf_default_state.sh >> "$(PURRFILE_TEMP)" # Load the binding libraries; this is the main block of code for purr. # Order here isn't super important, but the stream bindings need to be # at the very bottom after the command suites are fully initialized. - cat $(SRCDIR)/bindings/command_suites.sh >> "$(PURRFILE)" - cat $(SRCDIR)/bindings/copy_bindings.sh >> "$(PURRFILE)" - cat $(SRCDIR)/bindings/history_bindings.sh >> "$(PURRFILE)" - cat $(SRCDIR)/bindings/input_trim_bindings.sh >> "$(PURRFILE)" - cat $(SRCDIR)/bindings/misc_bindings.sh >> "$(PURRFILE)" - cat $(SRCDIR)/bindings/mode_bindings.sh >> "$(PURRFILE)" - cat $(SRCDIR)/bindings/navigation_bindings.sh >> "$(PURRFILE)" - cat $(SRCDIR)/bindings/preview_bindings.sh >> "$(PURRFILE)" - cat $(SRCDIR)/bindings/query_bindings.sh >> "$(PURRFILE)" - cat $(SRCDIR)/bindings/stream_bindings.sh >> "$(PURRFILE)" + cat $(SRCDIR)/bindings/command_suites.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/bindings/copy_bindings.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/bindings/history_bindings.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/bindings/input_trim_bindings.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/bindings/misc_bindings.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/bindings/mode_bindings.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/bindings/navigation_bindings.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/bindings/preview_bindings.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/bindings/query_bindings.sh >> "$(PURRFILE_TEMP)" + cat $(SRCDIR)/bindings/stream_bindings.sh >> "$(PURRFILE_TEMP)" # Load the execution loop that actually makes things happen. - cat $(SRCDIR)/execution_loop.sh >> "$(PURRFILE)" + cat $(SRCDIR)/execution_loop.sh >> "$(PURRFILE_TEMP)" # Remove comments/shebangs. - sed -i -e '/^[ \t]*#/d' "$(PURRFILE)" + sed -i -e '/^[ \t]*#/d' "$(PURRFILE_TEMP)" # Add one shebang and the copyright notice. # We need a temp file since cat can't do it in-place. - cat $(RESDIR)/header/purr_header.txt $(PURRFILE) > $(PURRFILE_TEMP) - - # Undo the temp file swap - mv -f $(PURRFILE_TEMP) $(PURRFILE) + cat $(RESDIR)/header/purr_header.txt $(PURRFILE_TEMP) > $(PURRFILE) # Grant execution permission. chmod +rwx $(PURRFILE) @@ -78,13 +83,48 @@ purr: adb_mock: mkdir -p $(OUTDIR) + mkdir -p $(OBJDIR) + echo "" > $(ADBMOCKFILE) + echo "" > $(ADBMOCKFILE_TEMP) + + cat $(TESTDIR)/res/adb_log_example.sh >> "$(ADBMOCKFILE_TEMP)" + cat $(TESTDIR)/mocks/adb_mock.sh >> "$(ADBMOCKFILE_TEMP)" - cat $(TESTDIR)/mocks/adb_mock.sh >> "$(ADBMOCKFILE)" + # Remove comments/shebangs. + sed -i -e '/^[ \t]*#/d' "$(ADBMOCKFILE_TEMP)" + # Add one shebang and the copyright notice. + # We need a temp file since cat can't do it in-place. + cat $(RESDIR)/header/purr_header.txt $(ADBMOCKFILE_TEMP) > $(ADBMOCKFILE) + + # Grant execution permission. chmod +rwx $(ADBMOCKFILE) +.PHONY: file_tester + +file_tester: + mkdir -p $(OUTDIR) + mkdir -p $(OBJDIR) + + echo "" > $(FILETESTERFILE) + echo "" > $(FILETESTERFILE_TEMP) + + cat $(TESTDIR)/res/adb_log_example.sh >> "$(FILETESTERFILE_TEMP)" + cat $(TESTDIR)/validators/file_validator.sh >> "$(FILETESTERFILE_TEMP)" + + # Remove comments/shebangs. + sed -i -e '/^[ \t]*#/d' "$(FILETESTERFILE_TEMP)" + + # Add one shebang and the copyright notice. + # We need a temp file since cat can't do it in-place. + cat $(RESDIR)/header/purr_header.txt $(FILETESTERFILE_TEMP) > $(FILETESTERFILE) + + # Grant execution permission. + chmod +rwx $(FILETESTERFILE) + .PHONY: clean clean: [ -e $(OUTDIR) ] && rm -r $(OUTDIR) || true + [ -e $(OBJDIR) ] && rm -r $(OBJDIR) || true diff --git a/src/execution_loop.sh b/src/execution_loop.sh index 190ffd5..dd5adaf 100644 --- a/src/execution_loop.sh +++ b/src/execution_loop.sh @@ -31,8 +31,11 @@ while true; do __purr_set_start_preview - # Starts and runs the actual fzf process. - if [ ! -z $cached_query ]; then + if [[ "$fzf_exec_flag" = "false" ]]; then # Don't exec fzf if in a testing session. + sleep 5 + accepted="" + ret=$? + elif [ ! -z $cached_query ]; then # Starts and runs the actual fzf process. accepted=$(FZF_DEFAULT_COMMAND="$load_input_stream" fzf $starter_preview_command $fzfp $fzf_prompt $bind_commands $start_command --query=$cached_query) ret=$? else diff --git a/src/shell/argument_parsing.sh b/src/shell/argument_parsing.sh index 029bf03..55fe74d 100644 --- a/src/shell/argument_parsing.sh +++ b/src/shell/argument_parsing.sh @@ -16,7 +16,7 @@ REQUIRED_FZF_VERSION="0.40.0" -VERSION="2.0.1" +VERSION="2.0.2" USAGE=("purr" "\n[-q: Sets the default query]" @@ -47,13 +47,19 @@ __purr_get_composite_version() { # Parse argument flags. instruction_flag=true adb_cmd_loc="adb" -while getopts ':A:a:f:ivVq:' flags; do +delete_dir_flag=true +fzf_exec_flag=true +while getopts ':XA:D:a:f:ivVq:' flags; do case $flags in q) query_string="--query=${OPTARG}" ;; a) custom_adb_params=${OPTARG} ;; A) adb_cmd_loc=${OPTARG} ;; + D) + delete_dir_flag=false + dir_name=${OPTARG} ;; f) custom_fzf_params=${OPTARG} ;; i) instruction_flag=false ;; + X) fzf_exec_flag=false ;; v) echo $VERSION exit 0 diff --git a/src/shell/shell_env_init.sh b/src/shell/shell_env_init.sh index d6e4773..d2efce0 100644 --- a/src/shell/shell_env_init.sh +++ b/src/shell/shell_env_init.sh @@ -14,7 +14,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -dir_name=$(mktemp -d /tmp/purr.XXXXXXXXXX) +if [ -z $dir_name ]; then + dir_name=$(mktemp -d /tmp/purr.XXXXXXXXXX) +else + mkdir -p $dir_name +fi # Run cleanup if we exit abnormally. trap "__purr_cleanup $dir_name" INT TERM diff --git a/src/threads/thread_interface.sh b/src/threads/thread_interface.sh index cba9153..e921315 100644 --- a/src/threads/thread_interface.sh +++ b/src/threads/thread_interface.sh @@ -31,7 +31,7 @@ __purr_cleanup() { fi # Delete all of the cached state files. - if [ -d $dir_name ]; then + if [ -d $dir_name ] && [[ $delete_dir_flag = "true" ]]; then rm -r $dir_name &>/dev/null fi } diff --git a/tests/mocks/adb_mock.sh b/tests/mocks/adb_mock.sh index da4dbf9..8bc11ee 100644 --- a/tests/mocks/adb_mock.sh +++ b/tests/mocks/adb_mock.sh @@ -1,24 +1,11 @@ #!/usr/bin/env zsh -if grep -q "devices" <<< $@; then +if grep -q "devices" <<<$@; then echo "List of devices attached" echo "emulator-5554 device" -elif grep -q "wait-for-device" <<< $@; then - sleep 0.01; -elif grep -q "logcat" <<< $@; then - cat <<-END - --------- beginning of system - 11-30 11:51:17.111 520 565 V DisplayPowerController2[0]: Brightness [0.39763778] reason changing to: 'manual', previous reason: 'manual [ dim ]'. - 11-30 11:51:17.111 520 565 I DisplayPowerController2[0]: BrightnessEvent: disp=0, physDisp=local:4619827259835644672, brt=0.39763778, initBrt=0.05, rcmdBrt=NaN, preBrt=NaN, lux=0.0, preLux=0.0, hbmMax=1.0, hbmMode=off, rbcStrength=0, thrmMax=1.0, powerFactor=1.0, wasShortTermModelActive=false, flags=, reason=manual, autoBrightness=false, strategy=InvalidBrightnessStrategy - --------- beginning of main - 11-30 11:51:17.159 397 397 I android.hardware.lights-service.example: Lights setting state for id=1 to color ff101010 - 11-30 11:51:17.186 397 397 I android.hardware.lights-service.example: Lights setting state for id=1 to color ff111111 - 11-30 11:51:17.197 397 397 I android.hardware.lights-service.example: Lights setting state for id=1 to color ff121212 - 11-30 11:51:17.214 397 397 I android.hardware.lights-service.example: Lights setting state for id=1 to color ff131313 - 11-30 11:51:17.231 397 397 I android.hardware.lights-service.example: Lights setting state for id=1 to color ff141414 - 11-30 11:51:17.247 397 397 I android.hardware.lights-service.example: Lights setting state for id=1 to color ff151515 - 11-30 11:51:17.264 397 397 I android.hardware.lights-service.example: Lights setting state for id=1 to color ff161616 - 11-30 11:51:17.281 397 397 I android.hardware.lights-service.example: Lights setting state for id=1 to color ff171717 - END +elif grep -q "wait-for-device" <<<$@; then + sleep 0.01 +elif grep -q "logcat" <<<$@; then + echo $mocked_adb_output sleep 9999999 fi diff --git a/tests/res/adb_log_example.sh b/tests/res/adb_log_example.sh new file mode 100644 index 0000000..3d3d791 --- /dev/null +++ b/tests/res/adb_log_example.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env zsh + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +mocked_adb_output=$(cat <<-END +--------- beginning of system +11-30 11:51:17.111 520 565 V DisplayPowerController2[0]: Brightness [0.39763778] reason changing to: 'manual', previous reason: 'manual [ dim ]'. +11-30 11:51:17.111 520 565 I DisplayPowerController2[0]: BrightnessEvent: disp=0, physDisp=local:4619827259835644672, brt=0.39763778, initBrt=0.05, rcmdBrt=NaN, preBrt=NaN, lux=0.0, preLux=0.0, hbmMax=1.0, hbmMode=off, rbcStrength=0, thrmMax=1.0, powerFactor=1.0, wasShortTermModelActive=false, flags=, reason=manual, autoBrightness=false, strategy=InvalidBrightnessStrategy +--------- beginning of main +11-30 11:51:17.159 397 397 I android.hardware.lights-service.example: Lights setting state for id=1 to color ff101010 +11-30 11:51:17.186 397 397 I android.hardware.lights-service.example: Lights setting state for id=1 to color ff111111 +11-30 11:51:17.197 397 397 I android.hardware.lights-service.example: Lights setting state for id=1 to color ff121212 +11-30 11:51:17.214 397 397 I android.hardware.lights-service.example: Lights setting state for id=1 to color ff131313 +11-30 11:51:17.231 397 397 I android.hardware.lights-service.example: Lights setting state for id=1 to color ff141414 +11-30 11:51:17.247 397 397 I android.hardware.lights-service.example: Lights setting state for id=1 to color ff151515 +11-30 11:51:17.264 397 397 I android.hardware.lights-service.example: Lights setting state for id=1 to color ff161616 +11-30 11:51:17.281 397 397 I android.hardware.lights-service.example: Lights setting state for id=1 to color ff171717 +END +) diff --git a/tests/validators/file_validator.sh b/tests/validators/file_validator.sh new file mode 100755 index 0000000..9b53e32 --- /dev/null +++ b/tests/validators/file_validator.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env zsh + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +validate_runtime_purr_files() { + dir_name=$1 + + # Did we load the serial? + grep -q -- "emulator-5554" $dir_name/serial-cache.purr + grep -q -- "emulator-5554" $dir_name/connection-state.purr + echo "Serial was loaded." + + # Did the input stream load to verbose? + grep -q -- "verbose-input-cache.purr" $dir_name/input-stream.purr + grep -q -- "Verbose" $dir_name/stream-header.purr + echo "Input stream is verbose." + + # Is the handler processing IO? + if [ -s $dir_name/background-handler-IO.purr ]; then + return 1 + else + echo "Thread IO working." + fi + + # Did we start on the instruction preview? And it is visible? + grep -q -- "instruction" $dir_name/preview-command-cache.purr + grep -q -- "nohidden" $dir_name/preview-visibility-cache.purr + echo "Preview set to instruction." + + # Did we start with our modes in the correct states? + grep -q -- "Off" $dir_name/purr_unique_cache.purr + grep -q -- "Off" $dir_name/scroll-lock-header.purr + grep -q -- "Chronological" $dir_name/sort-header.purr + echo "Modes set to correct starting states." + + # Did purr correctly pick up the adb output? + verbose_cache_contents=$(cat $dir_name/verbose-input-cache.purr) + if [[ "$verbose_cache_contents" = "$mocked_adb_output" ]]; then + echo "Verbose cache is correct." + else + return 1 + fi +} + +validate_exit_time_purr_files() { + dir_name=$1 + + # Has the handler been told to clean up? + grep -q -- "purr_thread_cleanup" $dir_name/background-handler-IO.purr + echo "Threads are in cleanup." + + # Did the handler actually do clean up? + if [ ! -f $dir_name/verbose-input-cache.purr ]; then + echo "Threads did actually cleanup." + else + return 1 + fi +} + +USAGE=("purr_file_validator" + "-a: Set the ADB binary path; likely the bundled adb_mock." + "-p: Set the purr binary path.") + +while getopts ':p:a:' flags; do + case $flags in + a) adb_mock_path=${OPTARG} ;; + p) purr_binary_path=${OPTARG} ;; + *) echo $USAGE ;; + esac +done + +if [ -z $purr_binary_path ]; then + echo >&2 "Please provide the path to the purr binary through -p." + exit 1 +elif [ -z $adb_mock_path ]; then + echo >&2 "Please provide the path to the adb mock binary through -a." + exit 1 +fi + +# We need to specify this so we know where purr is going to put +# the files. +dir_name=$(mktemp -d /tmp/purr.XXXXXXXXXX) + +# Fail on any error. +set -e + +# Run purr in the background with the given ADB binary. +# -X makes sure we don't actually launch fzf and only do file validation. +# When in -X mode, purr sleeps for 5 seconds after reaching fzf. +{ + eval "$purr_binary_path -A $adb_mock_path -D $dir_name -X" +} & + +# Wait for purr to start. +sleep 1 + +# Check that purr is OK during runtime. +validate_runtime_purr_files $dir_name + +# Wait for purr to exit. +sleep 5 + +# Check that purr exited OK. +validate_exit_time_purr_files $dir_name + +rm -r $dir_name + +echo "Files looks valid!"