You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
270 lines
10 KiB
270 lines
10 KiB
/*
|
|
* 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;
|
|
}
|