| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed 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 <sstream> |
| #include <unordered_map> |
| #include <vector> |
| |
| #include <jni.h> |
| #include <nativehelper/ScopedLocalRef.h> |
| #include <nativetesthelper_jni/utils.h> |
| #include <gtest/gtest.h> |
| |
| static JavaVM* gVm = nullptr; |
| JavaVM* GetJavaVM() { |
| return gVm; |
| } |
| static void RegisterJavaVm(JNIEnv* env) { |
| (void)env->GetJavaVM(&gVm); |
| } |
| |
| namespace { |
| |
| struct { |
| jclass clazz; |
| |
| /** static methods **/ |
| jmethodID createTestDescription; |
| |
| /** methods **/ |
| jmethodID addChild; |
| } gDescription; |
| |
| struct { |
| jclass clazz; |
| |
| jmethodID fireTestStarted; |
| jmethodID fireTestIgnored; |
| jmethodID fireTestFailure; |
| jmethodID fireTestFinished; |
| |
| } gRunNotifier; |
| |
| struct { |
| jclass clazz; |
| jmethodID ctor; |
| } gAssertionFailure; |
| |
| struct { |
| jclass clazz; |
| jmethodID ctor; |
| } gFailure; |
| |
| jobject gEmptyAnnotationsArray; |
| |
| struct TestNameInfo { |
| std::string nativeName; |
| bool run; |
| }; |
| // Maps mangled test names to native test names. |
| std::unordered_map<std::string, TestNameInfo> gNativeTestNames; |
| |
| // Return the full native test name as a Java method name, which does not allow |
| // slashes or dots. Store the original name for later lookup. |
| std::string registerAndMangleTestName(const std::string& nativeName) { |
| std::string mangledName = nativeName; |
| std::replace(mangledName.begin(), mangledName.end(), '.', '_'); |
| std::replace(mangledName.begin(), mangledName.end(), '/', '_'); |
| gNativeTestNames.insert(std::make_pair(mangledName, TestNameInfo{nativeName, false})); |
| return mangledName; |
| } |
| |
| // Creates org.junit.runner.Description object for a GTest given its name. |
| jobject createTestDescription(JNIEnv* env, jstring className, const std::string& mangledName) { |
| ScopedLocalRef<jstring> jTestName(env, env->NewStringUTF(mangledName.c_str())); |
| return env->CallStaticObjectMethod(gDescription.clazz, gDescription.createTestDescription, |
| className, jTestName.get(), gEmptyAnnotationsArray); |
| } |
| |
| jobject createTestDescription(JNIEnv* env, jstring className, const char* testCaseName, const char* testName) { |
| std::ostringstream nativeNameStream; |
| nativeNameStream << testCaseName << "." << testName; |
| std::string mangledName = registerAndMangleTestName(nativeNameStream.str()); |
| return createTestDescription(env, className, mangledName); |
| } |
| |
| void addChild(JNIEnv* env, jobject description, jobject childDescription) { |
| env->CallVoidMethod(description, gDescription.addChild, childDescription); |
| } |
| |
| |
| class JUnitNotifyingListener : public ::testing::EmptyTestEventListener { |
| public: |
| |
| JUnitNotifyingListener(JNIEnv* env, jstring className, jobject runNotifier) |
| : mEnv(env) |
| , mRunNotifier(runNotifier) |
| , mClassName(className) |
| , mCurrentTestDescription{env, nullptr} |
| {} |
| virtual ~JUnitNotifyingListener() {} |
| |
| virtual void OnTestStart(const testing::TestInfo &testInfo) override { |
| mCurrentTestDescription.reset( |
| createTestDescription(mEnv, mClassName, testInfo.test_case_name(), testInfo.name())); |
| notify(gRunNotifier.fireTestStarted); |
| } |
| |
| virtual void OnTestPartResult(const testing::TestPartResult &testPartResult) override { |
| if (!testPartResult.passed()) { |
| mCurrentTestError << "\n" << testPartResult.file_name() << ":" << testPartResult.line_number() |
| << "\n" << testPartResult.message() << "\n"; |
| } |
| } |
| |
| virtual void OnTestEnd(const testing::TestInfo&) override { |
| const std::string error = mCurrentTestError.str(); |
| |
| if (!error.empty()) { |
| ScopedLocalRef<jstring> jmessage(mEnv, mEnv->NewStringUTF(error.c_str())); |
| ScopedLocalRef<jobject> jthrowable(mEnv, mEnv->NewObject(gAssertionFailure.clazz, |
| gAssertionFailure.ctor, jmessage.get())); |
| ScopedLocalRef<jobject> jfailure(mEnv, mEnv->NewObject(gFailure.clazz, |
| gFailure.ctor, mCurrentTestDescription.get(), jthrowable.get())); |
| mEnv->CallVoidMethod(mRunNotifier, gRunNotifier.fireTestFailure, jfailure.get()); |
| } |
| |
| notify(gRunNotifier.fireTestFinished); |
| mCurrentTestDescription.reset(); |
| mCurrentTestError.str(""); |
| mCurrentTestError.clear(); |
| } |
| |
| void reportDisabledTests(const std::vector<std::string>& mangledNames) { |
| for (const std::string& mangledName : mangledNames) { |
| mCurrentTestDescription.reset(createTestDescription(mEnv, mClassName, mangledName)); |
| notify(gRunNotifier.fireTestIgnored); |
| mCurrentTestDescription.reset(); |
| } |
| } |
| |
| private: |
| void notify(jmethodID method) { |
| mEnv->CallVoidMethod(mRunNotifier, method, mCurrentTestDescription.get()); |
| } |
| |
| JNIEnv* mEnv; |
| jobject mRunNotifier; |
| jstring mClassName; |
| ScopedLocalRef<jobject> mCurrentTestDescription; |
| std::ostringstream mCurrentTestError; |
| }; |
| |
| } // namespace |
| |
| extern "C" |
| JNIEXPORT void JNICALL |
| Java_com_android_gtestrunner_GtestRunner_nInitialize(JNIEnv *env, jclass, jstring className, jobject suite) { |
| RegisterJavaVm(env); |
| |
| // Initialize gtest, removing the default result printer |
| int argc = 1; |
| const char* argv[] = { "gtest_wrapper" }; |
| ::testing::InitGoogleTest(&argc, (char**) argv); |
| |
| auto& listeners = ::testing::UnitTest::GetInstance()->listeners(); |
| delete listeners.Release(listeners.default_result_printer()); |
| |
| gDescription.clazz = (jclass) env->NewGlobalRef(env->FindClass("org/junit/runner/Description")); |
| gDescription.createTestDescription = env->GetStaticMethodID(gDescription.clazz, "createTestDescription", |
| "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/annotation/Annotation;)Lorg/junit/runner/Description;"); |
| gDescription.addChild = env->GetMethodID(gDescription.clazz, "addChild", |
| "(Lorg/junit/runner/Description;)V"); |
| |
| jclass annotations = env->FindClass("java/lang/annotation/Annotation"); |
| gEmptyAnnotationsArray = env->NewGlobalRef(env->NewObjectArray(0, annotations, nullptr)); |
| gNativeTestNames.clear(); |
| |
| gAssertionFailure.clazz = (jclass) env->NewGlobalRef(env->FindClass("java/lang/AssertionError")); |
| gAssertionFailure.ctor = env->GetMethodID(gAssertionFailure.clazz, "<init>", "(Ljava/lang/Object;)V"); |
| |
| gFailure.clazz = (jclass) env->NewGlobalRef(env->FindClass("org/junit/runner/notification/Failure")); |
| gFailure.ctor = env->GetMethodID(gFailure.clazz, "<init>", |
| "(Lorg/junit/runner/Description;Ljava/lang/Throwable;)V"); |
| |
| gRunNotifier.clazz = (jclass) env->NewGlobalRef( |
| env->FindClass("org/junit/runner/notification/RunNotifier")); |
| gRunNotifier.fireTestStarted = env->GetMethodID(gRunNotifier.clazz, "fireTestStarted", |
| "(Lorg/junit/runner/Description;)V"); |
| gRunNotifier.fireTestIgnored = env->GetMethodID(gRunNotifier.clazz, "fireTestIgnored", |
| "(Lorg/junit/runner/Description;)V"); |
| gRunNotifier.fireTestFinished = env->GetMethodID(gRunNotifier.clazz, "fireTestFinished", |
| "(Lorg/junit/runner/Description;)V"); |
| gRunNotifier.fireTestFailure = env->GetMethodID(gRunNotifier.clazz, "fireTestFailure", |
| "(Lorg/junit/runner/notification/Failure;)V"); |
| |
| auto unitTest = ::testing::UnitTest::GetInstance(); |
| for (int testCaseIndex = 0; testCaseIndex < unitTest->total_test_case_count(); testCaseIndex++) { |
| auto testCase = unitTest->GetTestCase(testCaseIndex); |
| for (int testIndex = 0; testIndex < testCase->total_test_count(); testIndex++) { |
| auto testInfo = testCase->GetTestInfo(testIndex); |
| ScopedLocalRef<jobject> testDescription(env, |
| createTestDescription(env, className, testCase->name(), testInfo->name())); |
| addChild(env, suite, testDescription.get()); |
| } |
| } |
| } |
| |
| extern "C" |
| JNIEXPORT void JNICALL |
| Java_com_android_gtestrunner_GtestRunner_nAddTest(JNIEnv *env, jclass, jstring testName) { |
| const char* testNameChars = env->GetStringUTFChars(testName, JNI_FALSE); |
| auto found = gNativeTestNames.find(testNameChars); |
| if (found != gNativeTestNames.end()) { |
| found->second.run = true; |
| } |
| env->ReleaseStringUTFChars(testName, testNameChars); |
| } |
| |
| extern "C" |
| JNIEXPORT jboolean JNICALL |
| Java_com_android_gtestrunner_GtestRunner_nRun(JNIEnv *env, jclass, jstring className, jobject notifier) { |
| // Apply the test filter computed in Java-land. The filter is just a list of test names. |
| std::ostringstream filterStream; |
| std::vector<std::string> mangledNamesOfDisabledTests; |
| for (const auto& entry : gNativeTestNames) { |
| // If the test was not selected for running by the Java layer, ignore it completely. |
| if (!entry.second.run) continue; |
| // If the test has DISABLED_ at the beginning of its name, after a slash or after a dot, |
| // report it as ignored (disabled) to the Java layer. |
| if (entry.second.nativeName.find("DISABLED_") == 0 || |
| entry.second.nativeName.find("/DISABLED_") != std::string::npos || |
| entry.second.nativeName.find(".DISABLED_") != std::string::npos) { |
| mangledNamesOfDisabledTests.push_back(entry.first); |
| continue; |
| } |
| filterStream << entry.second.nativeName << ":"; |
| } |
| std::string filter = filterStream.str(); |
| if (filter.empty()) { |
| // If the string we built is empty, we don't want to run any tests, but GTest runs all tests |
| // when an empty filter is passed. Replace an empty filter with a filter that matches nothing. |
| filter = "-*"; |
| } else { |
| // Removes the trailing colon. |
| filter.pop_back(); |
| } |
| ::testing::GTEST_FLAG(filter) = filter; |
| |
| auto& listeners = ::testing::UnitTest::GetInstance()->listeners(); |
| JUnitNotifyingListener junitListener{env, className, notifier}; |
| listeners.Append(&junitListener); |
| int success = RUN_ALL_TESTS(); |
| listeners.Release(&junitListener); |
| junitListener.reportDisabledTests(mangledNamesOfDisabledTests); |
| return success == 0; |
| } |