John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2017 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 17 | #include <sstream> |
| 18 | #include <unordered_map> |
| 19 | #include <vector> |
| 20 | |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 21 | #include <jni.h> |
| 22 | #include <nativehelper/ScopedLocalRef.h> |
| 23 | #include <gtest/gtest.h> |
| 24 | |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 25 | namespace { |
| 26 | |
| 27 | struct { |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 28 | jclass clazz; |
| 29 | |
| 30 | /** static methods **/ |
| 31 | jmethodID createTestDescription; |
| 32 | |
| 33 | /** methods **/ |
| 34 | jmethodID addChild; |
| 35 | } gDescription; |
| 36 | |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 37 | struct { |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 38 | jclass clazz; |
| 39 | |
| 40 | jmethodID fireTestStarted; |
| 41 | jmethodID fireTestIgnored; |
| 42 | jmethodID fireTestFailure; |
| 43 | jmethodID fireTestFinished; |
| 44 | |
| 45 | } gRunNotifier; |
| 46 | |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 47 | struct { |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 48 | jclass clazz; |
| 49 | jmethodID ctor; |
| 50 | } gAssertionFailure; |
| 51 | |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 52 | struct { |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 53 | jclass clazz; |
| 54 | jmethodID ctor; |
| 55 | } gFailure; |
| 56 | |
| 57 | jobject gEmptyAnnotationsArray; |
| 58 | |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 59 | struct TestNameInfo { |
| 60 | std::string nativeName; |
| 61 | bool run; |
| 62 | }; |
| 63 | // Maps mangled test names to native test names. |
| 64 | std::unordered_map<std::string, TestNameInfo> gNativeTestNames; |
| 65 | |
| 66 | // Return the full native test name as a Java method name, which does not allow |
| 67 | // slashes or dots. Store the original name for later lookup. |
| 68 | std::string registerAndMangleTestName(const std::string& nativeName) { |
| 69 | std::string mangledName = nativeName; |
| 70 | std::replace(mangledName.begin(), mangledName.end(), '.', '_'); |
| 71 | std::replace(mangledName.begin(), mangledName.end(), '/', '_'); |
| 72 | gNativeTestNames.insert(std::make_pair(mangledName, TestNameInfo{nativeName, false})); |
| 73 | return mangledName; |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 74 | } |
| 75 | |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 76 | // Creates org.junit.runner.Description object for a GTest given its name. |
| 77 | jobject createTestDescription(JNIEnv* env, jstring className, const std::string& mangledName) { |
| 78 | ScopedLocalRef<jstring> jTestName(env, env->NewStringUTF(mangledName.c_str())); |
| 79 | return env->CallStaticObjectMethod(gDescription.clazz, gDescription.createTestDescription, |
| 80 | className, jTestName.get(), gEmptyAnnotationsArray); |
| 81 | } |
| 82 | |
| 83 | jobject createTestDescription(JNIEnv* env, jstring className, const char* testCaseName, const char* testName) { |
| 84 | std::ostringstream nativeNameStream; |
| 85 | nativeNameStream << testCaseName << "." << testName; |
| 86 | std::string mangledName = registerAndMangleTestName(nativeNameStream.str()); |
| 87 | return createTestDescription(env, className, mangledName); |
| 88 | } |
| 89 | |
| 90 | void addChild(JNIEnv* env, jobject description, jobject childDescription) { |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 91 | env->CallVoidMethod(description, gDescription.addChild, childDescription); |
| 92 | } |
| 93 | |
| 94 | |
| 95 | class JUnitNotifyingListener : public ::testing::EmptyTestEventListener { |
| 96 | public: |
| 97 | |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 98 | JUnitNotifyingListener(JNIEnv* env, jstring className, jobject runNotifier) |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 99 | : mEnv(env) |
| 100 | , mRunNotifier(runNotifier) |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 101 | , mClassName(className) |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 102 | , mCurrentTestDescription{env, nullptr} |
| 103 | {} |
| 104 | virtual ~JUnitNotifyingListener() {} |
| 105 | |
| 106 | virtual void OnTestStart(const testing::TestInfo &testInfo) override { |
| 107 | mCurrentTestDescription.reset( |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 108 | createTestDescription(mEnv, mClassName, testInfo.test_case_name(), testInfo.name())); |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 109 | notify(gRunNotifier.fireTestStarted); |
| 110 | } |
| 111 | |
| 112 | virtual void OnTestPartResult(const testing::TestPartResult &testPartResult) override { |
| 113 | if (!testPartResult.passed()) { |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 114 | std::ostringstream messageStream; |
| 115 | messageStream << testPartResult.file_name() << ":" << testPartResult.line_number() |
| 116 | << "\n" << testPartResult.message(); |
| 117 | ScopedLocalRef<jstring> jmessage(mEnv, mEnv->NewStringUTF(messageStream.str().c_str())); |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 118 | ScopedLocalRef<jobject> jthrowable(mEnv, mEnv->NewObject(gAssertionFailure.clazz, |
| 119 | gAssertionFailure.ctor, jmessage.get())); |
| 120 | ScopedLocalRef<jobject> jfailure(mEnv, mEnv->NewObject(gFailure.clazz, |
| 121 | gFailure.ctor, mCurrentTestDescription.get(), jthrowable.get())); |
| 122 | mEnv->CallVoidMethod(mRunNotifier, gRunNotifier.fireTestFailure, jfailure.get()); |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | virtual void OnTestEnd(const testing::TestInfo&) override { |
| 127 | notify(gRunNotifier.fireTestFinished); |
| 128 | mCurrentTestDescription.reset(); |
| 129 | } |
| 130 | |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 131 | void reportDisabledTests(const std::vector<std::string>& mangledNames) { |
| 132 | for (const std::string& mangledName : mangledNames) { |
| 133 | mCurrentTestDescription.reset(createTestDescription(mEnv, mClassName, mangledName)); |
| 134 | notify(gRunNotifier.fireTestIgnored); |
| 135 | mCurrentTestDescription.reset(); |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 136 | } |
| 137 | } |
| 138 | |
| 139 | private: |
| 140 | void notify(jmethodID method) { |
| 141 | mEnv->CallVoidMethod(mRunNotifier, method, mCurrentTestDescription.get()); |
| 142 | } |
| 143 | |
| 144 | JNIEnv* mEnv; |
| 145 | jobject mRunNotifier; |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 146 | jstring mClassName; |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 147 | ScopedLocalRef<jobject> mCurrentTestDescription; |
| 148 | }; |
| 149 | |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 150 | } // namespace |
| 151 | |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 152 | extern "C" |
| 153 | JNIEXPORT void JNICALL |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 154 | Java_com_android_gtestrunner_GtestRunner_nInitialize(JNIEnv *env, jclass, jstring className, jobject suite) { |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 155 | // Initialize gtest, removing the default result printer |
| 156 | int argc = 1; |
| 157 | const char* argv[] = { "gtest_wrapper" }; |
| 158 | ::testing::InitGoogleTest(&argc, (char**) argv); |
| 159 | |
| 160 | auto& listeners = ::testing::UnitTest::GetInstance()->listeners(); |
| 161 | delete listeners.Release(listeners.default_result_printer()); |
| 162 | |
| 163 | gDescription.clazz = (jclass) env->NewGlobalRef(env->FindClass("org/junit/runner/Description")); |
| 164 | gDescription.createTestDescription = env->GetStaticMethodID(gDescription.clazz, "createTestDescription", |
| 165 | "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/annotation/Annotation;)Lorg/junit/runner/Description;"); |
| 166 | gDescription.addChild = env->GetMethodID(gDescription.clazz, "addChild", |
| 167 | "(Lorg/junit/runner/Description;)V"); |
| 168 | |
| 169 | jclass annotations = env->FindClass("java/lang/annotation/Annotation"); |
| 170 | gEmptyAnnotationsArray = env->NewGlobalRef(env->NewObjectArray(0, annotations, nullptr)); |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 171 | gNativeTestNames.clear(); |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 172 | |
| 173 | gAssertionFailure.clazz = (jclass) env->NewGlobalRef(env->FindClass("java/lang/AssertionError")); |
| 174 | gAssertionFailure.ctor = env->GetMethodID(gAssertionFailure.clazz, "<init>", "(Ljava/lang/Object;)V"); |
| 175 | |
| 176 | gFailure.clazz = (jclass) env->NewGlobalRef(env->FindClass("org/junit/runner/notification/Failure")); |
| 177 | gFailure.ctor = env->GetMethodID(gFailure.clazz, "<init>", |
| 178 | "(Lorg/junit/runner/Description;Ljava/lang/Throwable;)V"); |
| 179 | |
| 180 | gRunNotifier.clazz = (jclass) env->NewGlobalRef( |
| 181 | env->FindClass("org/junit/runner/notification/RunNotifier")); |
| 182 | gRunNotifier.fireTestStarted = env->GetMethodID(gRunNotifier.clazz, "fireTestStarted", |
| 183 | "(Lorg/junit/runner/Description;)V"); |
| 184 | gRunNotifier.fireTestIgnored = env->GetMethodID(gRunNotifier.clazz, "fireTestIgnored", |
| 185 | "(Lorg/junit/runner/Description;)V"); |
| 186 | gRunNotifier.fireTestFinished = env->GetMethodID(gRunNotifier.clazz, "fireTestFinished", |
| 187 | "(Lorg/junit/runner/Description;)V"); |
| 188 | gRunNotifier.fireTestFailure = env->GetMethodID(gRunNotifier.clazz, "fireTestFailure", |
| 189 | "(Lorg/junit/runner/notification/Failure;)V"); |
| 190 | |
| 191 | auto unitTest = ::testing::UnitTest::GetInstance(); |
| 192 | for (int testCaseIndex = 0; testCaseIndex < unitTest->total_test_case_count(); testCaseIndex++) { |
| 193 | auto testCase = unitTest->GetTestCase(testCaseIndex); |
| 194 | for (int testIndex = 0; testIndex < testCase->total_test_count(); testIndex++) { |
| 195 | auto testInfo = testCase->GetTestInfo(testIndex); |
| 196 | ScopedLocalRef<jobject> testDescription(env, |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 197 | createTestDescription(env, className, testCase->name(), testInfo->name())); |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 198 | addChild(env, suite, testDescription.get()); |
| 199 | } |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | extern "C" |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 204 | JNIEXPORT void JNICALL |
| 205 | Java_com_android_gtestrunner_GtestRunner_nAddTest(JNIEnv *env, jclass, jstring testName) { |
| 206 | const char* testNameChars = env->GetStringUTFChars(testName, JNI_FALSE); |
| 207 | auto found = gNativeTestNames.find(testNameChars); |
| 208 | if (found != gNativeTestNames.end()) { |
| 209 | found->second.run = true; |
| 210 | } |
| 211 | env->ReleaseStringUTFChars(testName, testNameChars); |
| 212 | } |
| 213 | |
| 214 | extern "C" |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 215 | JNIEXPORT jboolean JNICALL |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 216 | Java_com_android_gtestrunner_GtestRunner_nRun(JNIEnv *env, jclass, jstring className, jobject notifier) { |
| 217 | // Apply the test filter computed in Java-land. The filter is just a list of test names. |
| 218 | std::ostringstream filterStream; |
| 219 | std::vector<std::string> mangledNamesOfDisabledTests; |
| 220 | for (const auto& entry : gNativeTestNames) { |
| 221 | // If the test was not selected for running by the Java layer, ignore it completely. |
| 222 | if (!entry.second.run) continue; |
| 223 | // If the test has DISABLED_ at the beginning of its name, after a slash or after a dot, |
| 224 | // report it as ignored (disabled) to the Java layer. |
| 225 | if (entry.second.nativeName.find("DISABLED_") == 0 || |
| 226 | entry.second.nativeName.find("/DISABLED_") != std::string::npos || |
| 227 | entry.second.nativeName.find(".DISABLED_") != std::string::npos) { |
| 228 | mangledNamesOfDisabledTests.push_back(entry.first); |
| 229 | continue; |
| 230 | } |
| 231 | filterStream << entry.second.nativeName << ":"; |
| 232 | } |
| 233 | std::string filter = filterStream.str(); |
| 234 | if (filter.empty()) { |
| 235 | // If the string we built is empty, we don't want to run any tests, but GTest runs all tests |
| 236 | // when an empty filter is passed. Replace an empty filter with a filter that matches nothing. |
| 237 | filter = "-*"; |
| 238 | } else { |
| 239 | // Removes the trailing colon. |
| 240 | filter.pop_back(); |
| 241 | } |
| 242 | ::testing::GTEST_FLAG(filter) = filter; |
| 243 | |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 244 | auto& listeners = ::testing::UnitTest::GetInstance()->listeners(); |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 245 | JUnitNotifyingListener junitListener{env, className, notifier}; |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 246 | listeners.Append(&junitListener); |
| 247 | int success = RUN_ALL_TESTS(); |
| 248 | listeners.Release(&junitListener); |
Krzysztof Kosiński | 8189d41 | 2018-06-25 21:23:52 -0700 | [diff] [blame] | 249 | junitListener.reportDisabledTests(mangledNamesOfDisabledTests); |
John Reck | 98eaa48 | 2017-07-31 18:45:39 -0700 | [diff] [blame] | 250 | return success == 0; |
| 251 | } |