xref: /aosp_15_r20/tools/asuite/atest/atest_completion.sh (revision c2e18aaa1096c836b086f94603d04f4eb9cf37f5)
1# Copyright 2018, The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15# This function returns devices recognised by adb.
16_fetch_adb_devices() {
17    while read dev; do echo $dev | awk '{print $1}'; done < <(adb devices | egrep -v "^List|^$"||true)
18}
19
20# This function returns all paths contain TEST_MAPPING.
21_fetch_test_mapping_files() {
22    [[ -z $ANDROID_BUILD_TOP ]] && return 0
23    find -maxdepth 5 -type f -name TEST_MAPPING |sed 's/^.\///g'| xargs dirname 2>/dev/null
24}
25
26function _pip_install() {
27    _deb_installer python3-venv python3-pip || return 1
28    _activate_venv || return 1
29    requirements=(venv pyinstrument snakeviz)
30    for mod in "${requirements[@]}"; do
31        if ! _has_py_module $mod; then
32            echo "Installing $mod..."
33            pip3 install $mod >/dev/null
34            if [ "$?" -ne 0 ]; then
35                echo "pip3 install $mod failure."
36                return 1
37            fi
38        fi
39    done
40}
41
42function _has_py_module() {
43    [[ -z "$1" ]] && { echo "requires a module name."; return 1; }
44
45    cmd="python3 -c '
46import importlib.util as ut
47print(0 if ut.find_spec(\"$1\") else 1)
48'"
49    return $(eval "$cmd")
50}
51
52function _deb_installer() {
53    [[ -z "$@" ]] && { echo "requires a package name."; return 1; }
54
55    declare -a missing_pkgs
56    for pkg in "$@"; do
57        if ! $(dpkg -l $pkg | egrep -q '^ii'); then
58            missing_pkgs+=($pkg)
59        fi
60    done
61
62    if [ ${#missing_pkgs[@]} -gt 0 ]; then
63        echo -n "$(tput setaf 3)${missing_pkgs[@]}$(tput sgr0) are required. "
64        read -p "Do you want to procees? [N/y] " answer
65        case "$answer" in
66            [yY])
67                sudo apt install "${missing_pkgs[@]}" -y
68                ;;
69            *)
70                echo "No action taken. Exiting."; return 1
71                ;;
72        esac
73    fi
74}
75
76function _activate_venv() {
77    local VENV="$ANDROID_HOST_OUT/.atest_venv"
78    [[ ! -d "$VENV" ]] && python3 -m venv "$VENV"
79    source "$VENV/bin/activate" || {
80        echo unable to activate venv.
81        deactivate
82        return 1
83    }
84}
85
86function _atest_profile_cli() {
87    echo "_atest_profile_cli is deprecated. Use _atest_pyinstrument instead."
88    return 1
89}
90
91function _atest_pyinstrument() {
92    local T="$ANDROID_BUILD_TOP"
93    profile="$HOME/.atest/$(date +'%FT%H-%M-%S').pyisession"
94
95    _pip_install || return 1
96    m atest && python3 $T/tools/asuite/atest/profiler.py pyinstrument $profile\
97        $ANDROID_SOONG_HOST_OUT/bin/atest-dev --no-metrics "$@"
98    if [ "$?" -eq 0 ]; then
99        pyinstrument -t --load $profile || deactivate
100    fi
101    deactivate
102}
103
104function _atest_profile_web() {
105    echo _atest_profile_web is deprecated. Use _atest_cprofile_snakeviz instead.
106    return 1
107}
108
109function _atest_cprofile_snakeviz() {
110    local T="$ANDROID_BUILD_TOP"
111    profile="$HOME/.atest/$(date +'%F_%H-%M-%S').pstats"
112
113    _pip_install || return 1
114    m atest && python3 $T/tools/asuite/atest/profiler.py cProfile $profile \
115        $ANDROID_SOONG_HOST_OUT/bin/atest-dev --no-metrics "$@"
116    if [ "$?" -eq 0 ]; then
117        echo "$(tput bold)Use Ctrl-C to stop.$(tput sgr0)"
118        snakeviz $profile >/dev/null || deactivate
119    fi
120    deactivate
121}
122
123# The main tab completion function.
124_atest() {
125    COMPREPLY=()
126    local cmd=$(which $1)
127    local cur="${COMP_WORDS[COMP_CWORD]}"
128    local prev="${COMP_WORDS[COMP_CWORD-1]}"
129    _get_comp_words_by_ref -n : cur prev || true
130
131    if [[ "$cmd" == *prebuilts/asuite/atest/linux-x86/atest ]]; then
132        # prebuilts/asuite/atest/linux-x86/atest is shell script wrapper around
133        # atest-py3, which is what we should actually use.
134        cmd=$ANDROID_BUILD_TOP/prebuilts/asuite/atest/linux-x86/atest-py3
135    fi
136
137    case "$cur" in
138        -*)
139            COMPREPLY=($(compgen -W "$(unzip -p $cmd atest/atest_flag_list_for_completion.txt)" -- $cur))
140            ;;
141        */*)
142            ;;
143        *)
144            # Use grep instead of compgen -W because compgen -W is very slow. It takes
145            # ~0.7 seconds for compgen to read the all_modules.txt file.
146            # TODO(b/256228056) This fails if $cur has special characters in it
147            COMPREPLY=($(ls | grep "^$cur"; grep "^$cur" $ANDROID_PRODUCT_OUT/all_modules.txt 2>/dev/null))
148            ;;
149    esac
150
151    case "$prev" in
152        --iterations|--retry-any-failure|--rerun-until-failure)
153            COMPREPLY=(10) ;;
154        --list-modules|-L)
155            # TODO: genetate the list automately when the API is available.
156            COMPREPLY=($(compgen -W "cts vts" -- $cur)) ;;
157        --serial|-s)
158            local adb_devices="$(_fetch_adb_devices)"
159            if [ -n "$adb_devices" ]; then
160                COMPREPLY=($(compgen -W "$(_fetch_adb_devices)" -- $cur))
161            else
162                # Don't complete files/dirs when there'is no devices.
163                compopt -o nospace
164                COMPREPLY=("")
165            fi ;;
166        --test-mapping|-p)
167            local mapping_files="$(_fetch_test_mapping_files)"
168            if [ -n "$mapping_files" ]; then
169                COMPREPLY=($(compgen -W "$mapping_files" -- $cur))
170            else
171                # Don't complete files/dirs when TEST_MAPPING wasn't found.
172                compopt -o nospace
173                COMPREPLY=("")
174            fi ;;
175    esac
176    __ltrim_colon_completions "$cur" "$prev" || true
177    return 0
178}
179
180function _atest_main() {
181    # Only use this in interactive mode.
182    # Warning: below check must be "return", not "exit". "exit" won't break the
183    # build in interactive shell(e.g VM), but will result in build breakage in
184    # non-interactive shell(e.g docker container); therefore, using "return"
185    # adapts both conditions.
186    [[ ! $- =~ 'i' ]] && return 0
187
188    # Complete file/dir name first by using option "nosort".
189    # BASH version <= 4.3 doesn't have nosort option.
190    # Note that nosort has no effect for zsh.
191    local _atest_comp_options="-o default -o nosort"
192    local _atest_executables=(atest
193                              atest-dev
194                              atest-py3
195                              _atest_pyinstrument
196                              _atest_cprofile_snakeviz)
197    for exec in "${_atest_executables[*]}"; do
198        complete -F _atest $_atest_comp_options $exec 2>/dev/null || \
199        complete -F _atest -o default $exec
200    done
201
202    function atest-src() {
203        echo "atest-src is deprecated, use m atest && atest-dev instead" >&2
204        return 1
205    }
206
207    # Use prebuilt python3 for atest-dev
208    function atest-dev() {
209        atest_dev="$ANDROID_SOONG_HOST_OUT/bin/atest-dev"
210        if [ ! -f $atest_dev ]; then
211            echo "Cannot find atest-dev. Run 'm atest' to generate one."
212            return 1
213        fi
214        PREBUILT_TOOLS_DIR="$ANDROID_BUILD_TOP/prebuilts/build-tools/path/linux-x86"
215        PATH=$PREBUILT_TOOLS_DIR:$PATH $atest_dev "$@"
216    }
217}
218
219_atest_main
220