diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp index c39688cb8f2d5..0c40a6b5fa848 100644 --- a/startop/view_compiler/Android.bp +++ b/startop/view_compiler/Android.bp @@ -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", + ], +} diff --git a/startop/view_compiler/README.md b/startop/view_compiler/README.md index 56595016cbb99..f8da02b539070 100644 --- a/startop/view_compiler/README.md +++ b/startop/view_compiler/README.md @@ -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. diff --git a/startop/view_compiler/TEST_MAPPING b/startop/view_compiler/TEST_MAPPING new file mode 100644 index 0000000000000..5d675b76b3956 --- /dev/null +++ b/startop/view_compiler/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "dex-builder-test" + } + ] +} diff --git a/startop/view_compiler/dex_builder_test/Android.bp b/startop/view_compiler/dex_builder_test/Android.bp new file mode 100644 index 0000000000000..4449ea0f707ec --- /dev/null +++ b/startop/view_compiler/dex_builder_test/Android.bp @@ -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"], +} diff --git a/startop/view_compiler/dex_builder_test/AndroidManifest.xml b/startop/view_compiler/dex_builder_test/AndroidManifest.xml new file mode 100644 index 0000000000000..6ac5fc5db3456 --- /dev/null +++ b/startop/view_compiler/dex_builder_test/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/startop/view_compiler/dex_builder_test/AndroidTest.xml b/startop/view_compiler/dex_builder_test/AndroidTest.xml new file mode 100644 index 0000000000000..6f90cf3b81a7e --- /dev/null +++ b/startop/view_compiler/dex_builder_test/AndroidTest.xml @@ -0,0 +1,34 @@ + + + + diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java new file mode 100644 index 0000000000000..87b25780aaa86 --- /dev/null +++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java @@ -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!")); + } +} diff --git a/startop/view_compiler/dex_testcase_generator.cc b/startop/view_compiler/dex_testcase_generator.cc new file mode 100644 index 0000000000000..898817b4768c8 --- /dev/null +++ b/startop/view_compiler/dex_testcase_generator.cc @@ -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 +#include + +// 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(), 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(), image.size()); +} + +int main(int argc, char** argv) { + CHECK_EQ(argc, 2); + + string outdir = argv[1]; + + GenerateTrivialDexFile(outdir); + GenerateSimpleTestCases(outdir); +}