diff --git a/docker/Dockerfile.ci_hexagon b/docker/Dockerfile.ci_hexagon index b98276dddd51..54bdf5d316f2 100644 --- a/docker/Dockerfile.ci_hexagon +++ b/docker/Dockerfile.ci_hexagon @@ -63,6 +63,7 @@ ENV CLANG_LLVM_HOME /opt/clang-llvm ENV LD_LIBRARY_PATH $LD_LIBRARY_PATH:/opt/clang-llvm/lib ENV PATH /opt/clang-llvm/bin:$PATH ENV HEXAGON_TOOLCHAIN "${HEXAGON_SDK_PATH}/tools/HEXAGON_Tools/8.5.08/Tools" +ENV HEXAGON_GTEST "${HEXAGON_SDK_PATH}/utils/googletest/gtest" # sccache COPY install/ubuntu_install_sccache.sh /install/ubuntu_install_sccache.sh diff --git a/tests/cpp-runtime/hexagon/run_unit_tests.cc b/tests/cpp-runtime/hexagon/run_unit_tests.cc new file mode 100644 index 000000000000..6ad770da3326 --- /dev/null +++ b/tests/cpp-runtime/hexagon/run_unit_tests.cc @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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. + */ + +#include +#include +#include + +#include +#include + +#include "../src/support/utils.h" + +namespace tvm { +namespace runtime { +namespace hexagon { + +class GtestPrinter : public testing::EmptyTestEventListener { + void OnTestProgramStart(const testing::UnitTest& unit_test) override { + gtest_out_ << "[==========] Running " << unit_test.test_to_run_count() << " test(s) from " + << unit_test.test_suite_to_run_count() << " test suite(s).\n"; + } + + void OnTestProgramEnd(const testing::UnitTest& unit_test) override { + gtest_out_ << "[==========] " << unit_test.test_to_run_count() << " test(s) from " + << unit_test.test_suite_to_run_count() << " test suite(s) ran. (" + << unit_test.elapsed_time() << " ms total)\n"; + gtest_out_ << "[ PASSED ] " << unit_test.successful_test_count() << " test(s)\n"; + + if (unit_test.failed_test_count()) { + gtest_out_ << "[ FAILED ] " << unit_test.failed_test_count() << " test(s)\n"; + } + } + + void OnTestSuiteStart(const testing::TestSuite& test_suite) override { + gtest_out_ << "[----------] " << test_suite.test_to_run_count() << " test(s) from " + << test_suite.name() << "\n"; + } + + void OnTestSuiteEnd(const testing::TestSuite& test_suite) override { + gtest_out_ << "[----------] " << test_suite.test_to_run_count() << " test(s) from " + << test_suite.name() << " (" << test_suite.elapsed_time() << " ms total)\n"; + } + + void OnTestStart(const testing::TestInfo& test_info) override { + gtest_out_ << "[ RUN ] " << test_info.test_suite_name() << "." << test_info.name() << "\n"; + } + + void OnTestEnd(const testing::TestInfo& test_info) override { + for (int i = 0; i < test_info.result()->total_part_count(); ++i) { + gtest_out_ << test_info.result()->GetTestPartResult(i).message() << "\n"; + } + if (test_info.result()->Passed()) { + gtest_out_ << "[ OK ]"; + } else { + gtest_out_ << "[ FAILED ]"; + } + gtest_out_ << " " << test_info.test_suite_name() << "." << test_info.name() << " (" + << test_info.result()->elapsed_time() << " ms)\n"; + } + + std::stringstream gtest_out_; + + public: + std::string GetOutput() { return gtest_out_.str(); } +}; + +TVM_REGISTER_GLOBAL("hexagon.run_unit_tests").set_body([](TVMArgs args, TVMRetValue* rv) { + // gtest args are passed into this packed func as a singular string + // split gtest args using delimiter and build argument vector + std::vector parsed_args = tvm::support::Split(args[0], ' '); + std::vector argv; + + // add executable name + argv.push_back(const_cast("hexagon_run_unit_tests")); + + // add parsed arguments + for (int i = 0; i < parsed_args.size(); ++i) { + argv.push_back(const_cast(parsed_args[i].data())); + } + + // end of parsed arguments + argv.push_back(nullptr); + + // set argument count + int argc = argv.size() - 1; + + // initialize gtest with arguments and run + ::testing::InitGoogleTest(&argc, argv.data()); + + // add printer to capture gtest output in a string + GtestPrinter* gprinter = new GtestPrinter(); + testing::TestEventListeners& listeners = testing::UnitTest::GetInstance()->listeners(); + listeners.Append(gprinter); + + int gtest_error_code = RUN_ALL_TESTS(); + std::string gtest_output = gprinter->GetOutput(); + std::stringstream gtest_error_code_and_output; + gtest_error_code_and_output << gtest_error_code << std::endl; + gtest_error_code_and_output << gtest_output; + *rv = gtest_error_code_and_output.str(); + delete gprinter; +}); + +} // namespace hexagon +} // namespace runtime +} // namespace tvm diff --git a/tests/python/contrib/test_hexagon/conftest.py b/tests/python/contrib/test_hexagon/conftest.py index 7a90317d5506..e09329b76b20 100644 --- a/tests/python/contrib/test_hexagon/conftest.py +++ b/tests/python/contrib/test_hexagon/conftest.py @@ -218,3 +218,13 @@ def aot_target(aot_host_target): yield aot_host_target else: assert False, "Incorrect AoT host target: {aot_host_target}. Options are [c, llvm]." + + +def pytest_addoption(parser): + parser.addoption("--gtest_args", action="store", default="") + + +def pytest_generate_tests(metafunc): + option_value = metafunc.config.option.gtest_args + if "gtest_args" in metafunc.fixturenames and option_value is not None: + metafunc.parametrize("gtest_args", [option_value]) diff --git a/tests/python/contrib/test_hexagon/test_run_unit_tests.py b/tests/python/contrib/test_hexagon/test_run_unit_tests.py new file mode 100644 index 000000000000..3a383d30e5f4 --- /dev/null +++ b/tests/python/contrib/test_hexagon/test_run_unit_tests.py @@ -0,0 +1,47 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# http://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. + +import os +import pytest +import numpy as np +from tvm.contrib.hexagon.build import HexagonLauncher +from .conftest import requires_hexagon_toolchain + + +# use pytest -sv to observe gtest output +# use --gtest_args to pass arguments to gtest +# for example to run all "foo" tests twice and observe gtest output run +# pytest -sv --gtests_args="--gtest_filter=*foo* --gtest_repeat=2" +@requires_hexagon_toolchain +@pytest.mark.skipif( + os.environ.get("HEXAGON_GTEST") == None, + reason="Test requires environment variable HEXAGON_GTEST set with a path to a Hexagon gtest version normally located at /path/to/hexagon/sdk/utils/googletest/gtest", +) +def test_run_unit_tests(hexagon_session, gtest_args): + try: + func = hexagon_session._rpc.get_function("hexagon.run_unit_tests") + except: + print( + "Test requires TVM Runtime to be built with a Hexagon gtest version using Hexagon API cmake flag -DUSE_HEXAGON_GTEST=${HEXAGON_GTEST}" + ) + raise + + gtest_error_code_and_output = func(gtest_args) + gtest_error_code = int(gtest_error_code_and_output.splitlines()[0]) + gtest_output = gtest_error_code_and_output.split("\n", 1)[-1] + print(gtest_output) + np.testing.assert_equal(gtest_error_code, 0) diff --git a/tests/python/contrib/test_hexagon/unit_tests.py b/tests/python/contrib/test_hexagon/unit_tests.py deleted file mode 100644 index d340cba5b150..000000000000 --- a/tests/python/contrib/test_hexagon/unit_tests.py +++ /dev/null @@ -1,42 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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 -# -# http://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. - -import pytest -import numpy as np -from tvm.contrib.hexagon.build import HexagonLauncher -from .conftest import requires_hexagon_toolchain - - -@requires_hexagon_toolchain -def test_cache_read_write_2d(hexagon_session): - # arguments to pass to gtest - # e.g. - # 1) to run all tests use: - # gtest_args = "" - # 2) to run all tests with "foo" in their name twice use: - # gtest_args = "--gtest_repeat=2 --gtest_filter=*foo*" - gtest_args = "" - try: - func = hexagon_session._rpc.get_function("hexagon.run_all_tests") - result = func(gtest_args) - except: - print( - "This test requires the USE_HEXAGON_GTEST cmake flag to be specified with a path to a Hexagon gtest version normally located at /path/to/hexagon/sdk/utils/googletest/gtest" - ) - result = 1 - - np.testing.assert_equal(result, 0) diff --git a/tests/scripts/task_build_hexagon_api.sh b/tests/scripts/task_build_hexagon_api.sh index a3b501d9c554..c5d05eaad80c 100755 --- a/tests/scripts/task_build_hexagon_api.sh +++ b/tests/scripts/task_build_hexagon_api.sh @@ -37,6 +37,9 @@ cd build output_binary_directory=$(realpath ${PWD}/../../../build/hexagon_api_output) rm -rf ${output_binary_directory} +# should be removed after Hexagon Docker update +export HEXAGON_GTEST="${HEXAGON_SDK_PATH}/utils/googletest/gtest" + cmake -DANDROID_ABI=arm64-v8a \ -DANDROID_PLATFORM=android-28 \ -DUSE_ANDROID_TOOLCHAIN="${ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake" \ @@ -44,6 +47,6 @@ cmake -DANDROID_ABI=arm64-v8a \ -DUSE_HEXAGON_SDK="${HEXAGON_SDK_PATH}" \ -DUSE_HEXAGON_TOOLCHAIN="${HEXAGON_TOOLCHAIN}" \ -DUSE_OUTPUT_BINARY_DIR="${output_binary_directory}" \ - -DUSE_HEXAGON_GTEST="${HEXAGON_SDK_PATH}/utils/googletest/gtest" .. + -DUSE_HEXAGON_GTEST="${HEXAGON_GTEST}" .. make -j$(nproc) diff --git a/tests/scripts/task_python_hexagon.sh b/tests/scripts/task_python_hexagon.sh index 274b348f0935..b639ac02a695 100755 --- a/tests/scripts/task_python_hexagon.sh +++ b/tests/scripts/task_python_hexagon.sh @@ -43,6 +43,9 @@ if [[ "${device_serial}" == "simulator" ]]; then export HEXAGON_SDK_ROOT=${HEXAGON_SDK_PATH} fi +# should be removed after Hexagon Docker update +export HEXAGON_GTEST="${HEXAGON_SDK_PATH}/utils/googletest/gtest" + export ANDROID_SERIAL_NUMBER=${device_serial} run_pytest ctypes python-contrib-hexagon tests/python/contrib/test_hexagon