blob: eda919cca6eb4ea4b36712d94c9fba93bff11d5d [file] [log] [blame]
John Reck98eaa482017-07-31 18:45:39 -07001/*
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ński8189d412018-06-25 21:23:52 -070017#include <sstream>
18#include <unordered_map>
19#include <vector>
20
John Reck98eaa482017-07-31 18:45:39 -070021#include <jni.h>
22#include <nativehelper/ScopedLocalRef.h>
23#include <gtest/gtest.h>
24
Krzysztof Kosiński8189d412018-06-25 21:23:52 -070025namespace {
26
27struct {
John Reck98eaa482017-07-31 18:45:39 -070028 jclass clazz;
29
30 /** static methods **/
31 jmethodID createTestDescription;
32
33 /** methods **/
34 jmethodID addChild;
35} gDescription;
36
Krzysztof Kosiński8189d412018-06-25 21:23:52 -070037struct {
John Reck98eaa482017-07-31 18:45:39 -070038 jclass clazz;
39
40 jmethodID fireTestStarted;
41 jmethodID fireTestIgnored;
42 jmethodID fireTestFailure;
43 jmethodID fireTestFinished;
44
45} gRunNotifier;
46
Krzysztof Kosiński8189d412018-06-25 21:23:52 -070047struct {
John Reck98eaa482017-07-31 18:45:39 -070048 jclass clazz;
49 jmethodID ctor;
50} gAssertionFailure;
51
Krzysztof Kosiński8189d412018-06-25 21:23:52 -070052struct {
John Reck98eaa482017-07-31 18:45:39 -070053 jclass clazz;
54 jmethodID ctor;
55} gFailure;
56
57jobject gEmptyAnnotationsArray;
58
Krzysztof Kosiński8189d412018-06-25 21:23:52 -070059struct TestNameInfo {
60 std::string nativeName;
61 bool run;
62};
63// Maps mangled test names to native test names.
64std::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.
68std::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 Reck98eaa482017-07-31 18:45:39 -070074}
75
Krzysztof Kosiński8189d412018-06-25 21:23:52 -070076// Creates org.junit.runner.Description object for a GTest given its name.
77jobject 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
83jobject 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
90void addChild(JNIEnv* env, jobject description, jobject childDescription) {
John Reck98eaa482017-07-31 18:45:39 -070091 env->CallVoidMethod(description, gDescription.addChild, childDescription);
92}
93
94
95class JUnitNotifyingListener : public ::testing::EmptyTestEventListener {
96public:
97
Krzysztof Kosiński8189d412018-06-25 21:23:52 -070098 JUnitNotifyingListener(JNIEnv* env, jstring className, jobject runNotifier)
John Reck98eaa482017-07-31 18:45:39 -070099 : mEnv(env)
100 , mRunNotifier(runNotifier)
Krzysztof Kosiński8189d412018-06-25 21:23:52 -0700101 , mClassName(className)
John Reck98eaa482017-07-31 18:45:39 -0700102 , mCurrentTestDescription{env, nullptr}
103 {}
104 virtual ~JUnitNotifyingListener() {}
105
106 virtual void OnTestStart(const testing::TestInfo &testInfo) override {
107 mCurrentTestDescription.reset(
Krzysztof Kosiński8189d412018-06-25 21:23:52 -0700108 createTestDescription(mEnv, mClassName, testInfo.test_case_name(), testInfo.name()));
John Reck98eaa482017-07-31 18:45:39 -0700109 notify(gRunNotifier.fireTestStarted);
110 }
111
112 virtual void OnTestPartResult(const testing::TestPartResult &testPartResult) override {
113 if (!testPartResult.passed()) {
Krzysztof Kosiński8189d412018-06-25 21:23:52 -0700114 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 Reck98eaa482017-07-31 18:45:39 -0700118 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ński8189d412018-06-25 21:23:52 -0700131 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 Reck98eaa482017-07-31 18:45:39 -0700136 }
137 }
138
139private:
140 void notify(jmethodID method) {
141 mEnv->CallVoidMethod(mRunNotifier, method, mCurrentTestDescription.get());
142 }
143
144 JNIEnv* mEnv;
145 jobject mRunNotifier;
Krzysztof Kosiński8189d412018-06-25 21:23:52 -0700146 jstring mClassName;
John Reck98eaa482017-07-31 18:45:39 -0700147 ScopedLocalRef<jobject> mCurrentTestDescription;
148};
149
Krzysztof Kosiński8189d412018-06-25 21:23:52 -0700150} // namespace
151
John Reck98eaa482017-07-31 18:45:39 -0700152extern "C"
153JNIEXPORT void JNICALL
Krzysztof Kosiński8189d412018-06-25 21:23:52 -0700154Java_com_android_gtestrunner_GtestRunner_nInitialize(JNIEnv *env, jclass, jstring className, jobject suite) {
John Reck98eaa482017-07-31 18:45:39 -0700155 // 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ński8189d412018-06-25 21:23:52 -0700171 gNativeTestNames.clear();
John Reck98eaa482017-07-31 18:45:39 -0700172
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ński8189d412018-06-25 21:23:52 -0700197 createTestDescription(env, className, testCase->name(), testInfo->name()));
John Reck98eaa482017-07-31 18:45:39 -0700198 addChild(env, suite, testDescription.get());
199 }
200 }
201}
202
203extern "C"
Krzysztof Kosiński8189d412018-06-25 21:23:52 -0700204JNIEXPORT void JNICALL
205Java_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
214extern "C"
John Reck98eaa482017-07-31 18:45:39 -0700215JNIEXPORT jboolean JNICALL
Krzysztof Kosiński8189d412018-06-25 21:23:52 -0700216Java_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 Reck98eaa482017-07-31 18:45:39 -0700244 auto& listeners = ::testing::UnitTest::GetInstance()->listeners();
Krzysztof Kosiński8189d412018-06-25 21:23:52 -0700245 JUnitNotifyingListener junitListener{env, className, notifier};
John Reck98eaa482017-07-31 18:45:39 -0700246 listeners.Append(&junitListener);
247 int success = RUN_ALL_TESTS();
248 listeners.Release(&junitListener);
Krzysztof Kosiński8189d412018-06-25 21:23:52 -0700249 junitListener.reportDisabledTests(mangledNamesOfDisabledTests);
John Reck98eaa482017-07-31 18:45:39 -0700250 return success == 0;
251}