Add union output examples as a test
To help reviewers when there's changes affecting generated code.
For now C++ and Java supports "union" type, not NDK and Rust.
Bug: 150948558
Test: atest_unittests
Change-Id: Ia179f9b13f5889b120f037fef1e996235341bbb4
diff --git a/aidl_unittest.cpp b/aidl_unittest.cpp
index 8848c82..dcc931d 100644
--- a/aidl_unittest.cpp
+++ b/aidl_unittest.cpp
@@ -14,16 +14,18 @@
* limitations under the License.
*/
-#include <memory>
-#include <set>
-#include <string>
-#include <vector>
+#include "aidl.h"
#include <android-base/stringprintf.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
-#include "aidl.h"
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
#include "aidl_checkapi.h"
#include "aidl_language.h"
#include "aidl_to_cpp.h"
@@ -35,6 +37,7 @@
using android::aidl::internals::parse_preprocessed_file;
using android::aidl::test::FakeIoDelegate;
using android::base::StringPrintf;
+using std::map;
using std::set;
using std::string;
using std::unique_ptr;
@@ -2692,6 +2695,295 @@
EXPECT_EQ(expected_stderr, GetCapturedStderr());
}
+const char kUnionExampleExpectedOutputCppHeader[] = R"(#pragma once
+
+#include <a/ByteEnum.h>
+#include <binder/Parcel.h>
+#include <binder/Status.h>
+#include <cstdint>
+#include <utility>
+#include <variant>
+#include <vector>
+
+namespace a {
+
+class Foo : public ::android::Parcelable {
+public:
+ inline bool operator!=([[maybe_unused]] const Foo& rhs) const {
+ return _value!=rhs._value;
+ }
+ inline bool operator<([[maybe_unused]] const Foo& rhs) const {
+ return _value<rhs._value;
+ }
+ inline bool operator<=([[maybe_unused]] const Foo& rhs) const {
+ return _value<=rhs._value;
+ }
+ inline bool operator==([[maybe_unused]] const Foo& rhs) const {
+ return _value==rhs._value;
+ }
+ inline bool operator>([[maybe_unused]] const Foo& rhs) const {
+ return _value>rhs._value;
+ }
+ inline bool operator>=([[maybe_unused]] const Foo& rhs) const {
+ return _value>=rhs._value;
+ }
+ enum Tag : int32_t {
+ // int[] ns
+ ns = 0,
+ // a.ByteEnum e
+ e,
+ };
+ template<typename _Tp>
+ static constexpr bool not_self = !std::is_same_v<std::remove_cv_t<std::remove_reference_t<_Tp>>, Foo>;
+
+ Foo() : _value(std::in_place_index<ns>, ::std::vector<int32_t>({42})) { }
+ Foo(const Foo& other) = default;
+ Foo(Foo&& other) = default;
+ Foo& operator=(const Foo&) = default;
+ Foo& operator=(Foo&&) = default;
+
+ template <typename T, std::enable_if_t<not_self<T>, int> = 0>
+ constexpr Foo(T&& arg)
+ : _value(std::forward<T>(arg)) {}
+ template <typename... T>
+ constexpr explicit Foo(T&&... args)
+ : _value(std::forward<T>(args)...) {}
+ template <Tag tag, typename... T>
+ static Foo make(T&&... args) {
+ return Foo(std::in_place_index<tag>, std::forward<T>(args)...);
+ }
+ template <Tag tag, typename T, typename... U>
+ static Foo make(std::initializer_list<T> il, U&&... args) {
+ return Foo(std::in_place_index<tag>, std::move(il), std::forward<U>(args)...);
+ }
+
+ Tag getTag() const {
+ return (Tag)_value.index();
+ }
+
+ template <Tag tag>
+ const auto& get() const {
+ if (getTag() != tag) { abort(); }
+ return std::get<tag>(_value);
+ }
+
+ template <Tag tag>
+ auto& get() {
+ if (getTag() != tag) { abort(); }
+ return std::get<tag>(_value);
+ }
+
+ template <Tag tag, typename... T>
+ void set(T&&... args) {
+ _value.emplace<tag>(std::forward<T>(args)...);
+ }
+
+ ::android::status_t readFromParcel(const ::android::Parcel* _aidl_parcel) override final;
+ ::android::status_t writeToParcel(::android::Parcel* _aidl_parcel) const override final;
+ static const std::string& getParcelableDescriptor() {
+ static const std::string DESCIPTOR = "a.Foo";
+ return DESCIPTOR;
+ }
+private:
+ std::variant<::std::vector<int32_t>,::a::ByteEnum> _value;
+}; // class Foo
+
+} // namespace a
+)";
+
+const char kUnionExampleExpectedOutputCppSource[] = R"(#include <a/Foo.h>
+
+namespace a {
+
+::android::status_t Foo::readFromParcel(const ::android::Parcel* _aidl_parcel) {
+ ::android::status_t _aidl_ret_status;
+ int32_t _aidl_tag;
+ if ((_aidl_ret_status = _aidl_parcel->readInt32(&_aidl_tag)) != ::android::OK) return _aidl_ret_status;
+ switch (_aidl_tag) {
+ case ns: {
+ ::std::vector<int32_t> _aidl_value;
+ if ((_aidl_ret_status = _aidl_parcel->readInt32Vector(&_aidl_value)) != ::android::OK) return _aidl_ret_status;
+ set<ns>(std::move(_aidl_value));
+ return ::android::OK; }
+ case e: {
+ ::a::ByteEnum _aidl_value;
+ if ((_aidl_ret_status = _aidl_parcel->readByte(reinterpret_cast<int8_t *>(&_aidl_value))) != ::android::OK) return _aidl_ret_status;
+ set<e>(std::move(_aidl_value));
+ return ::android::OK; }
+ }
+ return ::android::BAD_VALUE;
+}
+
+::android::status_t Foo::writeToParcel(::android::Parcel* _aidl_parcel) const {
+ ::android::status_t _aidl_ret_status = _aidl_parcel->writeInt32(getTag());
+ if (_aidl_ret_status != ::android::OK) return _aidl_ret_status;
+ switch (getTag()) {
+ case ns: return _aidl_parcel->writeInt32Vector(get<ns>());
+ case e: return _aidl_parcel->writeByte(static_cast<int8_t>(get<e>()));
+ }
+ abort();
+}
+
+} // namespace a
+)";
+
+const char kUnionExampleExpectedOutputJava[] = R"(/*
+ * This file is auto-generated. DO NOT MODIFY.
+ */
+package a;
+
+
+public final class Foo implements android.os.Parcelable {
+ // tags union fields
+ public final static int ns = 0; // int[]
+ public final static int e = 1; // a.ByteEnum
+
+ private int _tag;
+ private Object _value;
+
+ public Foo() {
+ int[] value = {42};
+ _set(ns, value);
+ }
+ private Foo(android.os.Parcel _aidl_parcel) {
+ readFromParcel(_aidl_parcel);
+ }
+ private Foo(int tag, Object value) {
+ _set(tag, value);
+ }
+
+ public int getTag() {
+ return _tag;
+ }
+
+ // int[] ns
+
+ public static Foo ns(int[] _value) {
+ return new Foo(ns, _value);
+ }
+ public int[] getNs() {
+ _assertTag(ns);
+ return (int[]) _value;
+ }
+ public void setNs(int[] _value) {
+ _set(ns, _value);
+ }
+
+ // a.ByteEnum e
+
+ public static Foo e(byte _value) {
+ return new Foo(e, _value);
+ }
+ public byte getE() {
+ _assertTag(e);
+ return (byte) _value;
+ }
+ public void setE(byte _value) {
+ _set(e, _value);
+ }
+
+ public static final android.os.Parcelable.Creator<Foo> CREATOR = new android.os.Parcelable.Creator<Foo>() {
+ @Override
+ public Foo createFromParcel(android.os.Parcel _aidl_source) {
+ return new Union(_aidl_source);
+ }
+ @Override
+ public Foo[] newArray(int _aidl_size) {
+ return new Foo[_aidl_size];
+ }
+ };
+ @Override
+ public final void writeToParcel(android.os.Parcel _aidl_parcel, int _aidl_flag) {
+ _aidl_parcel.writeInt(_tag);
+ switch (_tag) {
+ case ns:
+ _aidl_parcel.writeIntArray(getNs());
+ break;
+ case e:
+ _aidl_parcel.writeByte(getE());
+ break;
+ }
+ }
+ public void readFromParcel(android.os.Parcel _aidl_parcel) {
+ int _aidl_tag;
+ _aidl_tag = _aidl_parcel.readInt();
+ switch (_aidl_tag) {
+ case ns: {
+ int[] _aidl_value;
+ _aidl_value = _aidl_parcel.createIntArray();
+ _set(_aidl_tag, _aidl_value);
+ return; }
+ case e: {
+ byte _aidl_value;
+ _aidl_value = _aidl_parcel.readByte();
+ _set(_aidl_tag, _aidl_value);
+ return; }
+ }
+ throw new RuntimeException("union: out of range: " + _aidl_tag);
+ }
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private void _assertTag(int tag) {
+ if (getTag() != tag) {
+ throw new IllegalStateException("bad access: " + _tagString(tag) + ", " + _tagString(tag) + " is available.");
+ }
+ }
+ private String _tagString(int _tag) {
+ switch (_tag) {
+ case ns: return "ns";
+ case e: return "e";
+ }
+ throw new IllegalStateException("unknown field: " + _tag);
+ }
+ private void _set(int tag, Object value) {
+ this._tag = tag;
+ this._value = value;
+ }
+}
+)";
+
+TEST_F(AidlTest, UnionExample) {
+ io_delegate_.SetFileContents("a/Foo.aidl", R"(
+package a;
+import a.ByteEnum;
+union Foo {
+ int[] ns = {42};
+ ByteEnum e;
+}
+)");
+ io_delegate_.SetFileContents("a/ByteEnum.aidl", R"(
+package a;
+@Backing(type="byte")
+enum ByteEnum {
+ a, b, c
+}
+)");
+
+ auto EXPECT_COMPILE_OUTPUT = [&](string lang, auto output) {
+ auto opts = Options::From("aidl a/Foo.aidl -I . --out=out --header_out=out --lang=" + lang);
+ CaptureStderr();
+ auto ret = ::android::aidl::compile_aidl(opts, io_delegate_);
+ auto err = GetCapturedStderr();
+ EXPECT_EQ(0, ret) << err;
+ for (const auto& [path, expected] : output) {
+ string actual;
+ EXPECT_TRUE(io_delegate_.GetWrittenContents(path, &actual)) << path << " not found.";
+ EXPECT_EQ(expected, actual);
+ }
+ };
+
+ EXPECT_COMPILE_OUTPUT(
+ "cpp", map<string, string>({{"out/a/Foo.cpp", kUnionExampleExpectedOutputCppSource},
+ {"out/a/Foo.h", kUnionExampleExpectedOutputCppHeader}}));
+ EXPECT_COMPILE_OUTPUT("java",
+ map<string, string>({{"out/a/Foo.java", kUnionExampleExpectedOutputJava}}));
+ // TODO(b/170784707) NDK
+ // TODO(b/170689477) Rust
+}
+
TEST_P(AidlTest, UnionRejectsEmptyDecl) {
const string method = "package a; union Foo {}";
const string expected_stderr = "ERROR: a/Foo.aidl:1.17-21: The union 'Foo' has no fields.\n";