/* * Copyright (C) 2012 Facebook, Inc. * * 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 com.facebook.swift.codec.metadata; import com.facebook.swift.codec.ThriftField; import com.facebook.swift.codec.ThriftStruct; import com.facebook.swift.codec.ThriftUnion; import com.facebook.swift.codec.ThriftUnionId; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import org.testng.annotations.Test; import java.lang.reflect.Field; import java.lang.reflect.Type; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import static org.fest.assertions.Assertions.assertThat; public class TestLegacyFieldIds { @Test public void testLegacyIdCorrectlyAnnotated() { ThriftStructMetadataBuilder builder = new ThriftStructMetadataBuilder(new ThriftCatalog(), LegacyIdCorrect.class); ThriftStructMetadata metadata = builder.build(); Set<Integer> seen = new HashSet<>(); for (ThriftFieldMetadata field : metadata.getFields()) { seen.add((int) field.getId()); } assertThat(seen) .as("fields found in LegacyIdCorrect") .isEqualTo(LegacyIdCorrect.IDS); } @Test public void testLegacyIdCorrectlyAnnotatedWhitebox() { ThriftStructMetadataBuilder builder = new ThriftStructMetadataBuilder(new ThriftCatalog(), LegacyIdCorrect.class); Set<Integer> seen = new HashSet<>(); for (FieldMetadata field : builder.fields) { String name = field.getName(); short id = field.getId(); boolean legacy = field.isLegacyId(); assertThat(name) .as("name of field " + field) .matches("^(notLegacy|legacy).*"); if (name.startsWith("legacy")) { assertThat(id) .as("id of field " + field) .isLessThan((short) 0); assertThat(legacy) .as("isLegacyId of field " + field) .isTrue(); } else { assertThat(id) .as("id of field " + field) .isGreaterThanOrEqualTo((short) 0); assertThat(legacy) .as("isLegacyId of field " + field) .isFalse(); } seen.add((int) id); } assertThat(seen) .as("present fields in the struct") .isEqualTo(LegacyIdCorrect.IDS); } @ThriftStruct public static final class LegacyIdCorrect { private static final Set<Integer> IDS = ImmutableSet.<Integer>of(-4, -3, -2, -1, 0, 1, 2); @ThriftField(value = 0, isLegacyId = false) public boolean notLegacyId0; @ThriftField(value = 1, isLegacyId = false) public boolean notLegacyId1; @ThriftField(value = 2) public boolean notLegacyId2; @ThriftField(value = -1, isLegacyId = true) public boolean legacyIdOnField; @ThriftField(value = -2, isLegacyId = true) public boolean getLegacyIdOnGetterOnly() { return false; } @ThriftField public void setLegacyIdOnGetterOnly(boolean value) { } @ThriftField public boolean getLegacyIdOnSetterOnly() { return false; } @ThriftField(value = -3, isLegacyId = true) public void setLegacyIdOnSetterOnly(boolean value) { } @ThriftField(value = -4, isLegacyId = true) public boolean getLegacyIdOnBoth() { return false; } @ThriftField(value = -4, isLegacyId = true) public void setLegacyIdOnBoth(boolean value) { } } @Test public void testLegacyIdIncorrect() { // 1: Missing isLegacyId=true when necessary { ThriftStructMetadataBuilder builder = new ThriftStructMetadataBuilder(new ThriftCatalog(), LegacyIdIncorrectlyMissing.class); MetadataErrors metadataErrors = builder.getMetadataErrors(); assertThat(metadataErrors.getErrors()) .as("metadata errors") .hasSize(1); assertThat(metadataErrors.getWarnings()) .as("metadata warnings") .isEmpty(); assertThat(metadataErrors.getErrors().get(0).getMessage()) .as("error message") .containsIgnoringCase("has a negative field id but not isLegacyId=true"); } // 2: Has isLegacyId=true when unnecessary { ThriftStructMetadataBuilder builder = new ThriftStructMetadataBuilder(new ThriftCatalog(), LegacyIdIncorrectlyPresent.class); MetadataErrors metadataErrors = builder.getMetadataErrors(); assertThat(metadataErrors.getErrors()) .as("metadata errors") .hasSize(1); assertThat(metadataErrors.getWarnings()) .as("metadata warnings") .isEmpty(); assertThat(metadataErrors.getErrors().get(0).getMessage()) .as("error message") .containsIgnoringCase("has isLegacyId=true but not a negative field id"); } // 3: Must be consistent for (Class<?> klass : Arrays.asList( LegacyIdInconsistent1.class, LegacyIdInconsistent2.class, LegacyIdInconsistent3.class, LegacyIdInconsistent4.class )) { ThriftStructMetadataBuilder builder = new ThriftStructMetadataBuilder(new ThriftCatalog(), klass); MetadataErrors metadataErrors = builder.getMetadataErrors(); assertThat(metadataErrors.getErrors()) .as("metadata errors") .isNotEmpty(); assertThat(metadataErrors.getWarnings()) .as("metadata warnings") .isEmpty(); assertThat(metadataErrors.getErrors().get(0).getMessage()) .as("error message") .containsIgnoringCase("has both isLegacyId=true and isLegacyId=false"); } } @ThriftStruct public static final class LegacyIdIncorrectlyMissing { @ThriftField(value = -4) public boolean field; } @ThriftStruct public static final class LegacyIdIncorrectlyPresent { @ThriftField(value = 4, isLegacyId = true) public boolean field; } /* legacy, getter correct, setter wrong */ @ThriftStruct public static final class LegacyIdInconsistent1 { @ThriftField(value = -4, isLegacyId = true) public boolean getField() { return false; } @ThriftField(value = -4, isLegacyId = false) public void setField(boolean value) { } } /* legacy, setter correct, getter wrong */ @ThriftStruct public static final class LegacyIdInconsistent2 { @ThriftField(value = -4, isLegacyId = false) public boolean getField() { return false; } @ThriftField(value = -4, isLegacyId = true) public void setField(boolean value) { } } /* not legacy, setter correct, getter wrong */ @ThriftStruct public static final class LegacyIdInconsistent3 { @ThriftField(value = 4, isLegacyId = true) public boolean getField() { return false; } @ThriftField(value = 4, isLegacyId = false) public void setField(boolean value) { } } /* not legacy, getter correct, setter wrong */ @ThriftStruct public static final class LegacyIdInconsistent4 { @ThriftField(value = 4, isLegacyId = false) public boolean getField() { return false; } @ThriftField(value = 4, isLegacyId = true) public void setField(boolean value) { } } @Test public void testGetThriftFieldIsLegacyId() { Function<FieldMetadata, Optional<Boolean>> getter = FieldMetadata.getThriftFieldIsLegacyId(); Function<ThriftField, FieldMetadata> makeFakeFieldMetadata = new Function<ThriftField, FieldMetadata>() { @Override public FieldMetadata apply(ThriftField input) { return new FieldMetadata(input, FieldKind.THRIFT_FIELD) { @Override public Type getJavaType() { throw new UnsupportedOperationException(); } @Override public String extractName() { throw new UnsupportedOperationException(); } }; } }; for (Field f : ReflectionHelper.findAnnotatedFields(SomeThriftFields.class, ThriftField.class)) { final Optional<Boolean> expected; if (f.getName().startsWith("expectTrue")) { expected = Optional.of(true); } else if (f.getName().startsWith("expectFalse")) { expected = Optional.of(false); } else if (f.getName().startsWith("expectNothing")) { expected = Optional.absent(); } else { Preconditions.checkArgument(f.getName().startsWith("broken")); continue; } Optional<Boolean> actual = getter.apply(makeFakeFieldMetadata.apply(f.getAnnotation(ThriftField.class))); assertThat(actual) .as("result of getThriftFieldIsLegacyId on " + f) .isEqualTo(expected); } } private static class SomeThriftFields { @ThriftField(value = +1, isLegacyId = false) boolean expectFalse1; @ThriftField(value = -1, isLegacyId = false) boolean expectFalse2; @ThriftField(isLegacyId = false) boolean broken; // see comments in impl. @ThriftField(value = +1, isLegacyId = true) boolean expectTrue1; @ThriftField(value = -1, isLegacyId = true) boolean expectTrue2; @ThriftField(isLegacyId = true) boolean expectTrue3; @ThriftField boolean expectNothing; } @Test public void testLegacyIdOnUnion() { ThriftUnionMetadataBuilder builder = new ThriftUnionMetadataBuilder(new ThriftCatalog(), LegacyIdUnionCorrect.class); ThriftStructMetadata metadata = builder.build(); Set<Integer> seen = new HashSet<>(); for (ThriftFieldMetadata field : metadata.getFields()) { seen.add((int) field.getId()); } assertThat(seen) .as("fields found in LegacyIdUnionCorrect") .isEqualTo(LegacyIdUnionCorrect.IDS); } @ThriftUnion public static final class LegacyIdUnionCorrect { private static final Set<Integer> IDS = ImmutableSet.<Integer>of(-4, -3, -2, -1, 0, 1, 2, (int) Short.MIN_VALUE); @ThriftUnionId public short unionId; @ThriftField(value = 0, isLegacyId = false) public boolean notLegacyId0; @ThriftField(value = 1, isLegacyId = false) public boolean notLegacyId1; @ThriftField(value = 2) public boolean notLegacyId2; @ThriftField(value = -1, isLegacyId = true) public boolean legacyIdOnField; @ThriftField(value = -2, isLegacyId = true) public boolean getLegacyIdOnGetterOnly() { return false; } @ThriftField public void setLegacyIdOnGetterOnly(boolean value) { } @ThriftField public boolean getLegacyIdOnSetterOnly() { return false; } @ThriftField(value = -3, isLegacyId = true) public void setLegacyIdOnSetterOnly(boolean value) { } @ThriftField(value = -4, isLegacyId = true) public boolean getLegacyIdOnBoth() { return false; } @ThriftField(value = -4, isLegacyId = true) public void setLegacyIdOnBoth(boolean value) { } } }