/* * Copyright 2012-present 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.buck.testutil; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.shell.ShellStep; import com.facebook.buck.step.ExecutionContext; import com.facebook.buck.step.Step; import com.facebook.buck.util.MoreCollectors; import com.facebook.buck.util.RichStream; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import com.google.common.io.ByteStreams; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; import org.junit.Assert; /** Additional assertions that delegate to JUnit assertions, but with better error messages. */ public final class MoreAsserts { private static final int BUFFER_SIZE = 8 * 1024; private MoreAsserts() {} /** * Asserts that two sets have the same contents. On failure, prints a readable diff of the two * sets for easy debugging. */ public static <E> void assertSetEquals(Set<E> expected, Set<E> actual) { Set<E> missing = Sets.difference(expected, actual); Set<E> extra = Sets.difference(actual, expected); boolean setsEqual = missing.isEmpty() && extra.isEmpty(); Assert.assertTrue( String.format("%nMissing elements:%n%s%nExtraneous elements:%n%s", missing, extra), setsEqual); } /** @see #assertIterablesEquals(Iterable, Iterable) */ public static <T extends List<?>> void assertListEquals(List<?> expected, List<?> observed) { assertIterablesEquals(expected, observed); } /** @see #assertIterablesEquals(String, Iterable, Iterable) */ public static <T extends List<?>> void assertListEquals( String userMessage, List<?> expected, List<?> observed) { assertIterablesEquals(userMessage, expected, observed); } /** * Equivalent to {@link org.junit.Assert#assertEquals(Object, Object)} except if the assertion * fails, the message includes information about where the iterables differ. */ public static <T extends Iterable<?>> void assertIterablesEquals( Iterable<?> expected, Iterable<?> observed) { assertIterablesEquals("" /* userMessage */, expected, observed); } /** * Equivalent to {@link org.junit.Assert#assertEquals(String, Object, Object)} except if the * assertion fails, the message includes information about where the iterables differ. */ public static <T extends Iterable<?>> void assertIterablesEquals( String userMessage, Iterable<?> expected, Iterable<?> observed) { // The traditional assertEquals() method should be fine if either List is null. if (expected == null || observed == null) { assertEquals(userMessage, expected, observed); return; } String errmsgPart = String.format( "expected:[%s] observed:[%s]", Joiner.on(", ").join(expected), Joiner.on(", ").join(observed)); // Compare each item in the list, one at a time. Iterator<?> expectedIter = expected.iterator(); Iterator<?> observedIter = observed.iterator(); int index = 0; while (expectedIter.hasNext()) { if (!observedIter.hasNext()) { fail( prefixWithUserMessage( userMessage, "Item " + index + " does not exist in the " + "observed list (" + errmsgPart + "): " + expectedIter.next())); } Object expectedItem = expectedIter.next(); Object observedItem = observedIter.next(); assertEquals( prefixWithUserMessage( userMessage, "Item " + index + " in the lists should match (" + errmsgPart + ")."), expectedItem, observedItem); ++index; } if (observedIter.hasNext()) { fail( prefixWithUserMessage( userMessage, "Extraneous item %s in the observed list (" + errmsgPart + "): %s.", index, observedIter.next())); } } public static <Item, Container extends Iterable<Item>> void assertContainsOne( Container container, Item expectedItem) { assertContainsOne(/* userMessage */ Iterables.toString(container), container, expectedItem); } public static <Item, Container extends Iterable<Item>> void assertContainsOne( String userMessage, Container container, Item expectedItem) { int seen = 0; for (Item item : container) { if (expectedItem.equals(item)) { seen++; } } if (seen < 1) { failWith( userMessage, "Item '" + expectedItem + "' not found in container, " + "expected to find one."); } if (seen > 1) { failWith( userMessage, "Found " + Integer.valueOf(seen) + " occurrences of '" + expectedItem + "' in container, expected to find only one."); } } /** * Asserts that every {@link com.facebook.buck.step.Step} in the observed list is a {@link * com.facebook.buck.shell.ShellStep} whose shell command arguments match those of the * corresponding entry in the expected list. */ public static void assertShellCommands( String userMessage, List<String> expected, List<Step> observed, ExecutionContext context) { Iterator<String> expectedIter = expected.iterator(); Iterator<Step> observedIter = observed.iterator(); Joiner joiner = Joiner.on(" "); while (expectedIter.hasNext() && observedIter.hasNext()) { String expectedShellCommand = expectedIter.next(); Step observedStep = observedIter.next(); if (!(observedStep instanceof ShellStep)) { failWith(userMessage, "Observed command must be a shell command: " + observedStep); } ShellStep shellCommand = (ShellStep) observedStep; String observedShellCommand = joiner.join(shellCommand.getShellCommand(context)); assertEquals(userMessage, expectedShellCommand, observedShellCommand); } if (expectedIter.hasNext()) { failWith(userMessage, "Extra expected command: " + expectedIter.next()); } if (observedIter.hasNext()) { failWith(userMessage, "Extra observed command: " + observedIter.next()); } } /** * Invokes the {@link Step#getDescription(ExecutionContext)} method on each of the observed steps * to create a list of strings and compares it to the expected value. */ public static void assertSteps( String userMessage, List<String> expectedStepDescriptions, List<Step> observedSteps, final ExecutionContext executionContext) { ImmutableList<String> commands = observedSteps .stream() .map(step -> step.getDescription(executionContext)) .collect(MoreCollectors.toImmutableList()); assertListEquals(userMessage, expectedStepDescriptions, commands); } public static void assertDepends(String userMessage, BuildRule rule, BuildRule dep) { assertDepends(userMessage, rule, dep.getBuildTarget()); } public static void assertDepends(String userMessage, BuildRule rule, BuildTarget dep) { assertDepends(userMessage, rule.getBuildDeps(), dep); } public static void assertDepends( String userMessage, Collection<BuildRule> ruleDeps, BuildTarget dep) { for (BuildRule realDep : ruleDeps) { BuildTarget target = realDep.getBuildTarget(); if (target.equals(dep)) { return; } } fail(userMessage); } public static <T> void assertOptionalValueEquals( String userMessage, T expectedValue, Optional<T> optionalValue) { if (!optionalValue.isPresent()) { failWith(userMessage, "Optional value is not present."); } assertEquals(userMessage, expectedValue, optionalValue.get()); } public static void assertContentsEqual(Path one, Path two) throws IOException { Preconditions.checkNotNull(one); Preconditions.checkNotNull(two); if (one.equals(two)) { return; } if (Files.size(one) != Files.size(two)) { fail( String.format( "File sizes differ: %s (%d bytes), %s (%d bytes)", one, Files.size(one), two, Files.size(two))); } try (InputStream ois = Files.newInputStream(one); InputStream tis = Files.newInputStream(two)) { byte[] bo = new byte[BUFFER_SIZE]; byte[] bt = new byte[BUFFER_SIZE]; while (true) { int read1 = ByteStreams.read(ois, bo, 0, BUFFER_SIZE); int read2 = ByteStreams.read(tis, bt, 0, BUFFER_SIZE); if (read1 != read2 || !Arrays.equals(bo, bt)) { fail(String.format("Contents of files differ: %s, %s", one, two)); } else if (read1 != BUFFER_SIZE) { return; } } } } /** * Asserts that two strings are equal, but compares them in chunks so that Intellij will show the * diffs when the assertion fails. */ public static void assertLargeStringsEqual(String expected, String content) { List<String> expectedChunks = chunkify(expected); List<String> contentChunks = chunkify(content); for (int i = 0; i < Math.min(expectedChunks.size(), contentChunks.size()); i++) { assertEquals("Failed at index: " + i, expectedChunks.get(i), contentChunks.get(i)); } // We could check this first, but it's usually more useful to see the first difference than to // just see that the two strings are different length. assertEquals(expectedChunks.size(), contentChunks.size()); } private static List<String> chunkify(String data) { return RichStream.from(Iterables.partition(Arrays.asList(data.split("\\n")), 1000)) .map((l) -> Joiner.on("\n").join(l)) .toImmutableList(); } private static String prefixWithUserMessage( @Nullable String userMessage, String message, Object... formatArgs) { return (userMessage == null ? "" : userMessage + " ") + String.format(message, formatArgs); } private static void failWith(@Nullable String userMessage, String message) { fail(prefixWithUserMessage(userMessage, message)); } }