// // Copyright © 2014, David Tesler (https://github.com/protobufel) // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of the <organization> nor the // names of its contributors may be used to endorse or promote products // derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // package com.github.protobufel.grammar; import static com.github.protobufel.grammar.Misc.getProtocFileDescriptorProto; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Recognizer; import org.assertj.core.api.JUnitSoftAssertions; import org.assertj.core.util.Files; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.github.protobufel.grammar.ErrorListeners.IBaseProtoErrorListener; import com.github.protobufel.grammar.ErrorListeners.LogProtoErrorListener; import com.github.protobufel.grammar.Misc.FieldTypeRefsMode; import com.github.protobufel.grammar.ProtoFiles.Builder; import com.google.common.base.Charsets; import com.google.protobuf.DescriptorProtos.FieldDescriptorProto; import com.google.protobuf.DescriptorProtos.FileDescriptorProto; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.TextFormat; @RunWith(JUnit4.class) public class DefaultValuesTest { private static final Logger log = LoggerFactory.getLogger(DefaultValuesTest.class); public static final int MAX_FIELD_NUMBER = 536870912; private static final String PROTOC_SUBDIR = "protoc/"; private final int TestExtremeDefaultValuesIndex = 0; private final int ProtocShouldNotConvertIndex = 1; private final int SameAsProtocIndex = 2; private final int SameAsProtoIndex = 3; private static final String DEFAULT_VALUES_PROTO = "defaults1.proto"; private File baseDir; // private List<String> files; @Mock private IBaseProtoErrorListener mockErrorListener; private LogProtoErrorListener errorListener; private ProtoFiles.Builder filesBuilder; private FileDescriptorProto protocProto; public final ExpectedException expected = ExpectedException.none(); public final JUnitSoftAssertions softly = new JUnitSoftAssertions(); @Rule public final TestRule chain = RuleChain.outerRule(expected).around(new MockitoJUnitRule(this)) .around(softly); private List<String> sameAsProtoDefaultValues; @Before public void setUp() throws Exception { // given errorListener = new LogProtoErrorListener(mockErrorListener).setLogger(getClass()); filesBuilder = ProtoFiles.newBuilder(errorListener).setProtocCompatible(false); final URI baseUri = getClass().getResource(PROTOC_SUBDIR).toURI(); baseDir = new File(baseUri); sameAsProtoDefaultValues = getSourceMessageDefaultValues(new File(baseDir, DEFAULT_VALUES_PROTO), "SameAsProto"); protocProto = getProtocFileDescriptorProto(DEFAULT_VALUES_PROTO, false, FieldTypeRefsMode.AS_IS); } @After public void tearDown() throws Exception {} @Test public void testFileDescriptorDefaultValuesAll() throws Exception { // given final FileDescriptor expectedFile = FileDescriptor.buildFrom(protocProto, new FileDescriptor[0]); final List<FieldDescriptor> expectedFields = getAllDefaultFields(expectedFile); // when final FileDescriptor actualFile = filesBuilder.setProtocCompatible(false).addFiles(baseDir, DEFAULT_VALUES_PROTO).build() .values().iterator().next(); final List<FieldDescriptor> actualFields = getAllDefaultFields(actualFile); // then softly.assertThat(actualFields).as("check nullness, duplicates, size").isNotNull() .doesNotContainNull().doesNotHaveDuplicates().hasSameSizeAs(expectedFields); softly.assertThat(actualFields).as("check field names equality") .usingElementComparatorOnFields("fullName").containsOnlyElementsOf(expectedFields); for (final FieldDescriptor actualField : actualFields) { final FieldDescriptor expectedField = expectedFields.get(actualField.getIndex()); softly.assertThat(actualField.getFullName()) .as("field %s name equal to expected", actualField.getFullName()) .isEqualTo(expectedField.getFullName()); softly.assertThat(actualField.getDefaultValue()) .as("field %s default value equal to expected", actualField.getFullName()) .isEqualTo(expectedField.getDefaultValue()); } } private List<FieldDescriptor> getAllDefaultFields(final FileDescriptor file) { // return file.getMessageTypes().get(TestExtremeDefaultValuesIndex).getFields(); final List<Descriptor> messageTypes = file.getMessageTypes(); final Descriptor descriptor = messageTypes.get(TestExtremeDefaultValuesIndex); return descriptor.getFields(); } @Test public void testExtremeDefaultValuesAll() throws Exception { try { assertEqualDescriptorProtoFields(TestExtremeDefaultValuesIndex, false); } catch (final AssertionError e) { // assuming protoc is wrong, so just log! log.info("protoc is wrong - should leave numeric defaults as in .proto source; " + "our parser is right!"); // log.debug("protoc differs benign warning", e); } } @Ignore // FIXME enable after fixing compatibility mode @Test public void testExtremeDefaultValuesAllInCompatibilityMode() throws Exception { assertEqualDescriptorProtoFields(TestExtremeDefaultValuesIndex, true); } @Test public void protocShouldNotConvert() throws Exception { try { assertEqualDescriptorProtoFields(ProtocShouldNotConvertIndex, false); } catch (final AssertionError e) { // assuming protoc is wrong, so just log! log.info("protoc is wrong - should leave numeric defaults as in .proto source; " + "our parser is right!"); // log.debug("protoc differs benign warning", e); } } @Ignore // FIXME enable after fixing compatibility mode @Test public void protocShouldNotConvertInCompatibilityMode() throws Exception { assertEqualDescriptorProtoFields(ProtocShouldNotConvertIndex, true); } @Test public void sameAsProtoc() throws Exception { assertEqualDescriptorProtoFields(SameAsProtocIndex, false); } @Ignore // FIXME enable after fixing compatibility mode @Test public void sameAsProtocInCompatibilityMode() throws Exception { assertEqualDescriptorProtoFields(SameAsProtocIndex, true); } @Test public void sameAsProto() throws Exception { sameAsProto(false); } @Ignore // FIXME enable after fixing compatibility mode @Test public void sameAsProtoInCompatibilityMode() throws Exception { sameAsProto(true); } private void sameAsProto(final boolean isProtocCompatible) throws Exception { // when final Builder protoBuilder = filesBuilder.setProtocCompatible(isProtocCompatible) .addFiles(baseDir, DEFAULT_VALUES_PROTO); final List<FieldDescriptorProto> actual = protoBuilder.buildProtos().get(0).getMessageType(SameAsProtoIndex).getFieldList(); // then final List<String> actualDefaultValues = getMessageProtoDefaultValues(actual); assertThat(actualDefaultValues).containsExactlyElementsOf(sameAsProtoDefaultValues); } private List<String> getMessageProtoDefaultValues(final List<FieldDescriptorProto> actual) throws IOException { final FieldDescriptor defaultValueFD = FieldDescriptorProto.getDescriptor().findFieldByName("default_value"); final List<String> actualDefaults = new ArrayList<String>(); for (final FieldDescriptorProto field : actual) { final StringBuilder sb = new StringBuilder(); TextFormat.printFieldValue(defaultValueFD, field.getDefaultValue(), sb); final String defaultValueText = sb.toString(); actualDefaults.add(defaultValueText.substring(1, defaultValueText.length() - 1)); } return actualDefaults; } private List<String> getSourceMessageDefaultValues(final File file, final String messageName) { final String source = Files.contentOf(file, Charsets.UTF_8); final Pattern defaultPattern = Pattern.compile("\\[.*?default\\s*=\\s*(.+?)\\s*\\];"); final String messageSource = source.split("message\\s+" + messageName + "\\s+\\{", 2)[1]; final Matcher matcher = defaultPattern.matcher(messageSource); final List<String> defaultValues = new ArrayList<String>(); while (matcher.find()) { String value = matcher.group(1); if (value.startsWith("\"")) { value = value.substring(1, value.length() - 1); } defaultValues.add(value); } return Collections.unmodifiableList(defaultValues); } private void assertEqualDescriptorProtoFields(final int messageIndex, final boolean isProtocCompatible) throws URISyntaxException, IOException { // given final List<FieldDescriptorProto> expected = protocProto.getMessageType(messageIndex).getFieldList(); // when final Builder protoBuilder = filesBuilder.setProtocCompatible(isProtocCompatible) .addFiles(baseDir, DEFAULT_VALUES_PROTO); final List<FieldDescriptorProto> actual = protoBuilder.buildProtos().get(0).getMessageType(messageIndex).getFieldList(); // then // no errors logged! verify(mockErrorListener, never()).validationError(anyInt(), anyInt(), anyString(), any(RuntimeException.class)); verify(mockErrorListener, never()).syntaxError(any(Recognizer.class), any(), anyInt(), anyInt(), anyString(), any(RecognitionException.class)); assertThat(actual).as("check nullness, duplicates, size").isNotNull().doesNotContainNull() .doesNotHaveDuplicates().hasSameSizeAs(expected); assertThat(actual).as("check fields equality").containsOnlyElementsOf(expected); } }