Skip to content

Commit 1a8c64b

Browse files
authored
[Hexagon] capture gtest output and return over FFI (#11239)
* [Hexagon] capture gtest output and return over FFI * rename to test_hexagon_unit_tests.py so it will run in CI * rename to run_unit_tests.cc * pass back gtest error code along with gtest output * skip Hexagon unit tests if gtest not enabled * pass gtest_args as pytest argument * change env variable to HEXAGON_GTEST * set HEXAGON_GTEST in the environment to enable Hexagon unit tests * add back try / except around get_function
1 parent 775457c commit 1a8c64b

File tree

7 files changed

+187
-43
lines changed

7 files changed

+187
-43
lines changed

docker/Dockerfile.ci_hexagon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ ENV CLANG_LLVM_HOME /opt/clang-llvm
6363
ENV LD_LIBRARY_PATH $LD_LIBRARY_PATH:/opt/clang-llvm/lib
6464
ENV PATH /opt/clang-llvm/bin:$PATH
6565
ENV HEXAGON_TOOLCHAIN "${HEXAGON_SDK_PATH}/tools/HEXAGON_Tools/8.5.08/Tools"
66+
ENV HEXAGON_GTEST "${HEXAGON_SDK_PATH}/utils/googletest/gtest"
6667

6768
# sccache
6869
COPY install/ubuntu_install_sccache.sh /install/ubuntu_install_sccache.sh
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include <gtest/gtest.h>
21+
#include <tvm/runtime/packed_func.h>
22+
#include <tvm/runtime/registry.h>
23+
24+
#include <string>
25+
#include <vector>
26+
27+
#include "../src/support/utils.h"
28+
29+
namespace tvm {
30+
namespace runtime {
31+
namespace hexagon {
32+
33+
class GtestPrinter : public testing::EmptyTestEventListener {
34+
void OnTestProgramStart(const testing::UnitTest& unit_test) override {
35+
gtest_out_ << "[==========] Running " << unit_test.test_to_run_count() << " test(s) from "
36+
<< unit_test.test_suite_to_run_count() << " test suite(s).\n";
37+
}
38+
39+
void OnTestProgramEnd(const testing::UnitTest& unit_test) override {
40+
gtest_out_ << "[==========] " << unit_test.test_to_run_count() << " test(s) from "
41+
<< unit_test.test_suite_to_run_count() << " test suite(s) ran. ("
42+
<< unit_test.elapsed_time() << " ms total)\n";
43+
gtest_out_ << "[ PASSED ] " << unit_test.successful_test_count() << " test(s)\n";
44+
45+
if (unit_test.failed_test_count()) {
46+
gtest_out_ << "[ FAILED ] " << unit_test.failed_test_count() << " test(s)\n";
47+
}
48+
}
49+
50+
void OnTestSuiteStart(const testing::TestSuite& test_suite) override {
51+
gtest_out_ << "[----------] " << test_suite.test_to_run_count() << " test(s) from "
52+
<< test_suite.name() << "\n";
53+
}
54+
55+
void OnTestSuiteEnd(const testing::TestSuite& test_suite) override {
56+
gtest_out_ << "[----------] " << test_suite.test_to_run_count() << " test(s) from "
57+
<< test_suite.name() << " (" << test_suite.elapsed_time() << " ms total)\n";
58+
}
59+
60+
void OnTestStart(const testing::TestInfo& test_info) override {
61+
gtest_out_ << "[ RUN ] " << test_info.test_suite_name() << "." << test_info.name() << "\n";
62+
}
63+
64+
void OnTestEnd(const testing::TestInfo& test_info) override {
65+
for (int i = 0; i < test_info.result()->total_part_count(); ++i) {
66+
gtest_out_ << test_info.result()->GetTestPartResult(i).message() << "\n";
67+
}
68+
if (test_info.result()->Passed()) {
69+
gtest_out_ << "[ OK ]";
70+
} else {
71+
gtest_out_ << "[ FAILED ]";
72+
}
73+
gtest_out_ << " " << test_info.test_suite_name() << "." << test_info.name() << " ("
74+
<< test_info.result()->elapsed_time() << " ms)\n";
75+
}
76+
77+
std::stringstream gtest_out_;
78+
79+
public:
80+
std::string GetOutput() { return gtest_out_.str(); }
81+
};
82+
83+
TVM_REGISTER_GLOBAL("hexagon.run_unit_tests").set_body([](TVMArgs args, TVMRetValue* rv) {
84+
// gtest args are passed into this packed func as a singular string
85+
// split gtest args using <space> delimiter and build argument vector
86+
std::vector<std::string> parsed_args = tvm::support::Split(args[0], ' ');
87+
std::vector<char*> argv;
88+
89+
// add executable name
90+
argv.push_back(const_cast<char*>("hexagon_run_unit_tests"));
91+
92+
// add parsed arguments
93+
for (int i = 0; i < parsed_args.size(); ++i) {
94+
argv.push_back(const_cast<char*>(parsed_args[i].data()));
95+
}
96+
97+
// end of parsed arguments
98+
argv.push_back(nullptr);
99+
100+
// set argument count
101+
int argc = argv.size() - 1;
102+
103+
// initialize gtest with arguments and run
104+
::testing::InitGoogleTest(&argc, argv.data());
105+
106+
// add printer to capture gtest output in a string
107+
GtestPrinter* gprinter = new GtestPrinter();
108+
testing::TestEventListeners& listeners = testing::UnitTest::GetInstance()->listeners();
109+
listeners.Append(gprinter);
110+
111+
int gtest_error_code = RUN_ALL_TESTS();
112+
std::string gtest_output = gprinter->GetOutput();
113+
std::stringstream gtest_error_code_and_output;
114+
gtest_error_code_and_output << gtest_error_code << std::endl;
115+
gtest_error_code_and_output << gtest_output;
116+
*rv = gtest_error_code_and_output.str();
117+
delete gprinter;
118+
});
119+
120+
} // namespace hexagon
121+
} // namespace runtime
122+
} // namespace tvm

tests/python/contrib/test_hexagon/conftest.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,13 @@ def aot_target(aot_host_target):
218218
yield aot_host_target
219219
else:
220220
assert False, "Incorrect AoT host target: {aot_host_target}. Options are [c, llvm]."
221+
222+
223+
def pytest_addoption(parser):
224+
parser.addoption("--gtest_args", action="store", default="")
225+
226+
227+
def pytest_generate_tests(metafunc):
228+
option_value = metafunc.config.option.gtest_args
229+
if "gtest_args" in metafunc.fixturenames and option_value is not None:
230+
metafunc.parametrize("gtest_args", [option_value])
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
import os
19+
import pytest
20+
import numpy as np
21+
from tvm.contrib.hexagon.build import HexagonLauncher
22+
from .conftest import requires_hexagon_toolchain
23+
24+
25+
# use pytest -sv to observe gtest output
26+
# use --gtest_args to pass arguments to gtest
27+
# for example to run all "foo" tests twice and observe gtest output run
28+
# pytest -sv <this file> --gtests_args="--gtest_filter=*foo* --gtest_repeat=2"
29+
@requires_hexagon_toolchain
30+
@pytest.mark.skipif(
31+
os.environ.get("HEXAGON_GTEST") == None,
32+
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",
33+
)
34+
def test_run_unit_tests(hexagon_session, gtest_args):
35+
try:
36+
func = hexagon_session._rpc.get_function("hexagon.run_unit_tests")
37+
except:
38+
print(
39+
"Test requires TVM Runtime to be built with a Hexagon gtest version using Hexagon API cmake flag -DUSE_HEXAGON_GTEST=${HEXAGON_GTEST}"
40+
)
41+
raise
42+
43+
gtest_error_code_and_output = func(gtest_args)
44+
gtest_error_code = int(gtest_error_code_and_output.splitlines()[0])
45+
gtest_output = gtest_error_code_and_output.split("\n", 1)[-1]
46+
print(gtest_output)
47+
np.testing.assert_equal(gtest_error_code, 0)

tests/python/contrib/test_hexagon/unit_tests.py

Lines changed: 0 additions & 42 deletions
This file was deleted.

tests/scripts/task_build_hexagon_api.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,16 @@ cd build
3737
output_binary_directory=$(realpath ${PWD}/../../../build/hexagon_api_output)
3838
rm -rf ${output_binary_directory}
3939

40+
# should be removed after Hexagon Docker update
41+
export HEXAGON_GTEST="${HEXAGON_SDK_PATH}/utils/googletest/gtest"
42+
4043
cmake -DANDROID_ABI=arm64-v8a \
4144
-DANDROID_PLATFORM=android-28 \
4245
-DUSE_ANDROID_TOOLCHAIN="${ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake" \
4346
-DUSE_HEXAGON_ARCH=v68 \
4447
-DUSE_HEXAGON_SDK="${HEXAGON_SDK_PATH}" \
4548
-DUSE_HEXAGON_TOOLCHAIN="${HEXAGON_TOOLCHAIN}" \
4649
-DUSE_OUTPUT_BINARY_DIR="${output_binary_directory}" \
47-
-DUSE_HEXAGON_GTEST="${HEXAGON_SDK_PATH}/utils/googletest/gtest" ..
50+
-DUSE_HEXAGON_GTEST="${HEXAGON_GTEST}" ..
4851

4952
make -j$(nproc)

tests/scripts/task_python_hexagon.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ if [[ "${device_serial}" == "simulator" ]]; then
4343
export HEXAGON_SDK_ROOT=${HEXAGON_SDK_PATH}
4444
fi
4545

46+
# should be removed after Hexagon Docker update
47+
export HEXAGON_GTEST="${HEXAGON_SDK_PATH}/utils/googletest/gtest"
48+
4649
export ANDROID_SERIAL_NUMBER=${device_serial}
4750
run_pytest ctypes python-contrib-hexagon tests/python/contrib/test_hexagon
4851

0 commit comments

Comments
 (0)