Merge "[view_compiler] Add end-to-end DexBuilder tests"

This commit is contained in:
Treehugger Robot
2018-11-09 01:10:33 +00:00
committed by Gerrit Code Review
8 changed files with 299 additions and 0 deletions

View File

@@ -62,3 +62,22 @@ cc_test_host {
],
test_suites: ["general-tests"],
}
cc_binary_host {
name: "dex_testcase_generator",
defaults: ["viewcompiler_defaults"],
srcs: ["dex_testcase_generator.cc"],
static_libs: [
"libviewcompiler",
],
}
genrule {
name: "generate_dex_testcases",
tools: [":dex_testcase_generator"],
cmd: "$(location :dex_testcase_generator) $(genDir)",
out: [
"simple.dex",
"trivial.dex",
],
}

View File

@@ -23,3 +23,31 @@ This tool is still in its early stages and has a number of limitations.
application.
* This only works for apps that do not use a custom layout inflater.
* Other limitations yet to be discovered.
## DexBuilder Tests
The DexBuilder has several low-level end to end tests to verify generated DEX
code validates, runs, and has the correct behavior. There are, unfortunately, a
number of pieces that must be added to generate new tests. Here are the
components:
* `dex_testcase_generator` - Written in C++ using `DexBuilder`. This runs as a
build step produce the DEX files that will be tested on device. See the
`genrule` named `generate_dex_testcases` in `Android.bp`. These files are then
copied over to the device by TradeFed when running tests.
* `DexBuilderTest` - This is a Java Language test harness that loads the
generated DEX files and exercises methods in the file.
To add a new DEX file test, follow these steps:
1. Modify `dex_testcase_generator` to produce the DEX file.
2. Add the filename to the `out` list of the `generate_dex_testcases` rule in
`Android.bp`.
3. Add a new `push` option to `AndroidTest.xml` to copy the DEX file to the
device.
4. Modify `DexBuilderTest.java` to load and exercise the new test.
In each case, you should be able to cargo-cult the existing test cases.
In general, you can probably get by without adding a new generated DEX file, and
instead add more methods to the files that are already generated. In this case,
you can skip all of steps 2 and 3 above, and simplify steps 1 and 4.

View File

@@ -0,0 +1,7 @@
{
"presubmit": [
{
"name": "dex-builder-test"
}
]
}

View File

@@ -0,0 +1,29 @@
//
// Copyright (C) 2018 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.
//
android_test {
name: "dex-builder-test",
srcs: ["src/android/startop/test/DexBuilderTest.java"],
sdk_version: "current",
data: [":generate_dex_testcases"],
static_libs: [
"android-support-test",
"guava",
],
manifest: "AndroidManifest.xml",
test_config: "AndroidTest.xml",
test_suites: ["general-tests"],
}

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.startop.test" >
<uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
<application>
<uses-library android:name="android.test.runner" />
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.startop.test"
android:label="DexBuilder Tests"/>
</manifest>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 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.
-->
<configuration description="Runs DexBuilder Tests.">
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-instrumentation" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="dex-builder-test.apk" />
</target_preparer>
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
<option name="push" value="trivial.dex->/data/local/tmp/dex-builder-test/trivial.dex" />
<option name="push" value="simple.dex->/data/local/tmp/dex-builder-test/simple.dex" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.startop.test" />
<option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
</test>
</configuration>

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2018 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.
*/
package android.startop.test;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import com.google.common.io.ByteStreams;
import dalvik.system.InMemoryDexClassLoader;
import dalvik.system.PathClassLoader;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import org.junit.Assert;
import org.junit.Test;
// Adding tests here requires changes in several other places. See README.md in
// the view_compiler directory for more information.
public class DexBuilderTest {
static ClassLoader loadDexFile(String filename) throws Exception {
return new PathClassLoader("/data/local/tmp/dex-builder-test/" + filename,
ClassLoader.getSystemClassLoader());
}
public void hello() {}
@Test
public void loadTrivialDex() throws Exception {
ClassLoader loader = loadDexFile("trivial.dex");
loader.loadClass("android.startop.test.testcases.Trivial");
}
@Test
public void return5() throws Exception {
ClassLoader loader = loadDexFile("simple.dex");
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
Method method = clazz.getMethod("return5");
Assert.assertEquals(5, method.invoke(null));
}
@Test
public void returnParam() throws Exception {
ClassLoader loader = loadDexFile("simple.dex");
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
Method method = clazz.getMethod("returnParam", int.class);
Assert.assertEquals(5, method.invoke(null, 5));
Assert.assertEquals(42, method.invoke(null, 42));
}
@Test
public void returnStringLength() throws Exception {
ClassLoader loader = loadDexFile("simple.dex");
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
Method method = clazz.getMethod("returnStringLength", String.class);
Assert.assertEquals(13, method.invoke(null, "Hello, World!"));
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (C) 2018 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 "android-base/logging.h"
#include "dex_builder.h"
#include <fstream>
#include <string>
// Adding tests here requires changes in several other places. See README.md in
// the view_compiler directory for more information.
using namespace startop::dex;
using namespace std;
void GenerateTrivialDexFile(const string& outdir) {
DexBuilder dex_file;
ClassBuilder cbuilder{dex_file.MakeClass("android.startop.test.testcases.Trivial")};
cbuilder.set_source_file("dex_testcase_generator.cc#GenerateTrivialDexFile");
slicer::MemView image{dex_file.CreateImage()};
std::ofstream out_file(outdir + "/trivial.dex");
out_file.write(image.ptr<const char>(), image.size());
}
// Generates test cases that test around 1 instruction.
void GenerateSimpleTestCases(const string& outdir) {
DexBuilder dex_file;
ClassBuilder cbuilder{dex_file.MakeClass("android.startop.test.testcases.SimpleTests")};
cbuilder.set_source_file("dex_testcase_generator.cc#GenerateSimpleTestCases");
// int return5() { return 5; }
auto return5{cbuilder.CreateMethod("return5", Prototype{TypeDescriptor::Int()})};
Value r{return5.MakeRegister()};
return5.BuildConst4(r, 5);
return5.BuildReturn(r);
return5.Encode();
// // int returnParam(int x) { return x; }
auto returnParam{cbuilder.CreateMethod("returnParam",
Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})};
returnParam.BuildReturn(Value::Parameter(0));
returnParam.Encode();
// int returnStringLength(String x) { return x.length(); }
auto string_type{TypeDescriptor::FromClassname("java.lang.String")};
MethodDeclData string_length{
dex_file.GetOrDeclareMethod(string_type, "length", Prototype{TypeDescriptor::Int()})};
auto returnStringLength{
cbuilder.CreateMethod("returnStringLength", Prototype{TypeDescriptor::Int(), string_type})};
Value result = returnStringLength.MakeRegister();
returnStringLength.AddInstruction(
Instruction::InvokeVirtual(string_length.id, result, Value::Parameter(0)));
returnStringLength.BuildReturn(result);
returnStringLength.Encode();
slicer::MemView image{dex_file.CreateImage()};
std::ofstream out_file(outdir + "/simple.dex");
out_file.write(image.ptr<const char>(), image.size());
}
int main(int argc, char** argv) {
CHECK_EQ(argc, 2);
string outdir = argv[1];
GenerateTrivialDexFile(outdir);
GenerateSimpleTestCases(outdir);
}