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 
17 #include <jni.h>
18 #include <nativehelper/ScopedLocalRef.h>
19 #include <gtest/gtest.h>
20 
21 static struct {
22     jclass clazz;
23 
24     /** static methods **/
25     jmethodID createTestDescription;
26 
27     /** methods **/
28     jmethodID addChild;
29 } gDescription;
30 
31 static struct {
32     jclass clazz;
33 
34     jmethodID fireTestStarted;
35     jmethodID fireTestIgnored;
36     jmethodID fireTestFailure;
37     jmethodID fireTestFinished;
38 
39 } gRunNotifier;
40 
41 static struct {
42     jclass clazz;
43     jmethodID ctor;
44 } gAssertionFailure;
45 
46 static struct {
47     jclass clazz;
48     jmethodID ctor;
49 } gFailure;
50 
51 jobject gEmptyAnnotationsArray;
52 
createTestDescription(JNIEnv * env,const char * className,const char * testName)53 static jobject createTestDescription(JNIEnv* env, const char* className, const char* testName) {
54     ScopedLocalRef<jstring> jClassName(env, env->NewStringUTF(className));
55     ScopedLocalRef<jstring> jTestName(env, env->NewStringUTF(testName));
56     return env->CallStaticObjectMethod(gDescription.clazz, gDescription.createTestDescription,
57             jClassName.get(), jTestName.get(), gEmptyAnnotationsArray);
58 }
59 
addChild(JNIEnv * env,jobject description,jobject childDescription)60 static void addChild(JNIEnv* env, jobject description, jobject childDescription) {
61     env->CallVoidMethod(description, gDescription.addChild, childDescription);
62 }
63 
64 
65 class JUnitNotifyingListener : public ::testing::EmptyTestEventListener {
66 public:
67 
JUnitNotifyingListener(JNIEnv * env,jobject runNotifier)68     JUnitNotifyingListener(JNIEnv* env, jobject runNotifier)
69             : mEnv(env)
70             , mRunNotifier(runNotifier)
71             , mCurrentTestDescription{env, nullptr}
72     {}
~JUnitNotifyingListener()73     virtual ~JUnitNotifyingListener() {}
74 
OnTestStart(const testing::TestInfo & testInfo)75     virtual void OnTestStart(const testing::TestInfo &testInfo) override {
76         mCurrentTestDescription.reset(
77                 createTestDescription(mEnv, testInfo.test_case_name(), testInfo.name()));
78         notify(gRunNotifier.fireTestStarted);
79     }
80 
OnTestPartResult(const testing::TestPartResult & testPartResult)81     virtual void OnTestPartResult(const testing::TestPartResult &testPartResult) override {
82         if (!testPartResult.passed()) {
83             char message[1024];
84             snprintf(message, 1024, "%s:%d\n%s", testPartResult.file_name(), testPartResult.line_number(),
85                     testPartResult.message());
86             ScopedLocalRef<jstring> jmessage(mEnv, mEnv->NewStringUTF(message));
87             ScopedLocalRef<jobject> jthrowable(mEnv, mEnv->NewObject(gAssertionFailure.clazz,
88                     gAssertionFailure.ctor, jmessage.get()));
89             ScopedLocalRef<jobject> jfailure(mEnv, mEnv->NewObject(gFailure.clazz,
90                     gFailure.ctor, mCurrentTestDescription.get(), jthrowable.get()));
91             mEnv->CallVoidMethod(mRunNotifier, gRunNotifier.fireTestFailure, jfailure.get());
92         }
93     }
94 
OnTestEnd(const testing::TestInfo &)95     virtual void OnTestEnd(const testing::TestInfo&) override {
96         notify(gRunNotifier.fireTestFinished);
97         mCurrentTestDescription.reset();
98     }
99 
OnTestProgramEnd(const testing::UnitTest & unitTest)100     virtual void OnTestProgramEnd(const testing::UnitTest& unitTest) override {
101         // Invoke the notifiers for all the disabled tests
102         for (int testCaseIndex = 0; testCaseIndex < unitTest.total_test_case_count(); testCaseIndex++) {
103             auto testCase = unitTest.GetTestCase(testCaseIndex);
104             for (int testIndex = 0; testIndex < testCase->total_test_count(); testIndex++) {
105                 auto testInfo = testCase->GetTestInfo(testIndex);
106                 if (!testInfo->should_run()) {
107                     mCurrentTestDescription.reset(
108                             createTestDescription(mEnv, testCase->name(), testInfo->name()));
109                     notify(gRunNotifier.fireTestIgnored);
110                     mCurrentTestDescription.reset();
111                 }
112             }
113         }
114     }
115 
116 private:
notify(jmethodID method)117     void notify(jmethodID method) {
118         mEnv->CallVoidMethod(mRunNotifier, method, mCurrentTestDescription.get());
119     }
120 
121     JNIEnv* mEnv;
122     jobject mRunNotifier;
123     ScopedLocalRef<jobject> mCurrentTestDescription;
124 };
125 
126 extern "C"
127 JNIEXPORT void JNICALL
Java_com_android_gtestrunner_GtestRunner_nInitialize(JNIEnv * env,jclass,jobject suite)128 Java_com_android_gtestrunner_GtestRunner_nInitialize(JNIEnv *env, jclass, jobject suite) {
129     // Initialize gtest, removing the default result printer
130     int argc = 1;
131     const char* argv[] = { "gtest_wrapper" };
132     ::testing::InitGoogleTest(&argc, (char**) argv);
133 
134     auto& listeners = ::testing::UnitTest::GetInstance()->listeners();
135     delete listeners.Release(listeners.default_result_printer());
136 
137     gDescription.clazz = (jclass) env->NewGlobalRef(env->FindClass("org/junit/runner/Description"));
138     gDescription.createTestDescription = env->GetStaticMethodID(gDescription.clazz, "createTestDescription",
139             "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/annotation/Annotation;)Lorg/junit/runner/Description;");
140     gDescription.addChild = env->GetMethodID(gDescription.clazz, "addChild",
141             "(Lorg/junit/runner/Description;)V");
142 
143     jclass annotations = env->FindClass("java/lang/annotation/Annotation");
144     gEmptyAnnotationsArray = env->NewGlobalRef(env->NewObjectArray(0, annotations, nullptr));
145 
146     gAssertionFailure.clazz = (jclass) env->NewGlobalRef(env->FindClass("java/lang/AssertionError"));
147     gAssertionFailure.ctor = env->GetMethodID(gAssertionFailure.clazz, "<init>", "(Ljava/lang/Object;)V");
148 
149     gFailure.clazz = (jclass) env->NewGlobalRef(env->FindClass("org/junit/runner/notification/Failure"));
150     gFailure.ctor = env->GetMethodID(gFailure.clazz, "<init>",
151             "(Lorg/junit/runner/Description;Ljava/lang/Throwable;)V");
152 
153     gRunNotifier.clazz = (jclass) env->NewGlobalRef(
154             env->FindClass("org/junit/runner/notification/RunNotifier"));
155     gRunNotifier.fireTestStarted = env->GetMethodID(gRunNotifier.clazz, "fireTestStarted",
156             "(Lorg/junit/runner/Description;)V");
157     gRunNotifier.fireTestIgnored = env->GetMethodID(gRunNotifier.clazz, "fireTestIgnored",
158             "(Lorg/junit/runner/Description;)V");
159     gRunNotifier.fireTestFinished = env->GetMethodID(gRunNotifier.clazz, "fireTestFinished",
160             "(Lorg/junit/runner/Description;)V");
161     gRunNotifier.fireTestFailure = env->GetMethodID(gRunNotifier.clazz, "fireTestFailure",
162             "(Lorg/junit/runner/notification/Failure;)V");
163 
164     auto unitTest = ::testing::UnitTest::GetInstance();
165     for (int testCaseIndex = 0; testCaseIndex < unitTest->total_test_case_count(); testCaseIndex++) {
166         auto testCase = unitTest->GetTestCase(testCaseIndex);
167         for (int testIndex = 0; testIndex < testCase->total_test_count(); testIndex++) {
168             auto testInfo = testCase->GetTestInfo(testIndex);
169             ScopedLocalRef<jobject> testDescription(env,
170                     createTestDescription(env, testCase->name(), testInfo->name()));
171             addChild(env, suite, testDescription.get());
172         }
173     }
174 }
175 
176 extern "C"
177 JNIEXPORT jboolean JNICALL
Java_com_android_gtestrunner_GtestRunner_nRun(JNIEnv * env,jclass,jobject notifier)178 Java_com_android_gtestrunner_GtestRunner_nRun(JNIEnv *env, jclass, jobject notifier) {
179     auto& listeners = ::testing::UnitTest::GetInstance()->listeners();
180     JUnitNotifyingListener junitListener{env, notifier};
181     listeners.Append(&junitListener);
182     int success = RUN_ALL_TESTS();
183     listeners.Release(&junitListener);
184     return success == 0;
185 }
186