|
@@ -0,0 +1,431 @@
|
|
|
+/*
|
|
|
+ * Licensed to the Apache Software Foundation (ASF) under one
|
|
|
+ * or more contributor license agreements. See the NOTICE file
|
|
|
+ * distributed with this work for additional information
|
|
|
+ * regarding copyright ownership. The ASF licenses this file
|
|
|
+ * to you 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 org.apache.hadoop.fs.impl;
|
|
|
+
|
|
|
+import java.util.EnumSet;
|
|
|
+
|
|
|
+import org.assertj.core.api.Assertions;
|
|
|
+import org.junit.Test;
|
|
|
+
|
|
|
+import org.apache.hadoop.conf.Configuration;
|
|
|
+import org.apache.hadoop.test.AbstractHadoopTestBase;
|
|
|
+
|
|
|
+import static java.util.EnumSet.allOf;
|
|
|
+import static java.util.EnumSet.noneOf;
|
|
|
+import static org.apache.hadoop.fs.impl.FlagSet.buildFlagSet;
|
|
|
+import static org.apache.hadoop.fs.impl.FlagSet.createFlagSet;
|
|
|
+import static org.apache.hadoop.test.LambdaTestUtils.intercept;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Unit tests for {@link FlagSet} class.
|
|
|
+ */
|
|
|
+public final class TestFlagSet extends AbstractHadoopTestBase {
|
|
|
+
|
|
|
+ private static final String KEY = "key";
|
|
|
+
|
|
|
+ public static final String CAPABILITY_B = KEY + ".b";
|
|
|
+
|
|
|
+ public static final String CAPABILITY_C = KEY + ".c";
|
|
|
+
|
|
|
+ public static final String CAPABILITY_A = KEY + ".a";
|
|
|
+
|
|
|
+ private static final String KEYDOT = KEY + ".";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Flagset used in tests and assertions.
|
|
|
+ */
|
|
|
+ private FlagSet<SimpleEnum> flagSet =
|
|
|
+ createFlagSet(SimpleEnum.class, KEYDOT, noneOf(SimpleEnum.class));
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Simple Enums for the tests.
|
|
|
+ */
|
|
|
+ private enum SimpleEnum { a, b, c }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Enum with a single value.
|
|
|
+ */
|
|
|
+ private enum OtherEnum { a }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Test that an entry can be enabled and disabled.
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testEntryEnableDisable() {
|
|
|
+ Assertions.assertThat(flagSet.flags()).isEmpty();
|
|
|
+ assertDisabled(SimpleEnum.a);
|
|
|
+ flagSet.enable(SimpleEnum.a);
|
|
|
+ assertEnabled(SimpleEnum.a);
|
|
|
+ flagSet.disable(SimpleEnum.a);
|
|
|
+ assertDisabled(SimpleEnum.a);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Test the setter.
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testSetMethod() {
|
|
|
+ Assertions.assertThat(flagSet.flags()).isEmpty();
|
|
|
+ flagSet.set(SimpleEnum.a, true);
|
|
|
+ assertEnabled(SimpleEnum.a);
|
|
|
+ flagSet.set(SimpleEnum.a, false);
|
|
|
+ assertDisabled(SimpleEnum.a);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Test mutability by making immutable and
|
|
|
+ * expecting setters to fail.
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testMutability() throws Throwable {
|
|
|
+ flagSet.set(SimpleEnum.a, true);
|
|
|
+ flagSet.makeImmutable();
|
|
|
+ intercept(IllegalStateException.class, () ->
|
|
|
+ flagSet.disable(SimpleEnum.a));
|
|
|
+ assertEnabled(SimpleEnum.a);
|
|
|
+ intercept(IllegalStateException.class, () ->
|
|
|
+ flagSet.set(SimpleEnum.a, false));
|
|
|
+ assertEnabled(SimpleEnum.a);
|
|
|
+ // now look at the setters
|
|
|
+ intercept(IllegalStateException.class, () ->
|
|
|
+ flagSet.enable(SimpleEnum.b));
|
|
|
+ assertDisabled(SimpleEnum.b);
|
|
|
+ intercept(IllegalStateException.class, () ->
|
|
|
+ flagSet.set(SimpleEnum.b, true));
|
|
|
+ assertDisabled(SimpleEnum.b);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Test stringification.
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testToString() throws Throwable {
|
|
|
+ // empty
|
|
|
+ assertStringValue("{}");
|
|
|
+ assertConfigurationStringMatches("");
|
|
|
+
|
|
|
+ // single value
|
|
|
+ flagSet.enable(SimpleEnum.a);
|
|
|
+ assertStringValue("{a}");
|
|
|
+ assertConfigurationStringMatches("a");
|
|
|
+
|
|
|
+ // add a second value.
|
|
|
+ flagSet.enable(SimpleEnum.b);
|
|
|
+ assertStringValue("{a, b}");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Assert that {@link FlagSet#toString()} matches the expected
|
|
|
+ * value.
|
|
|
+ * @param expected expected value
|
|
|
+ */
|
|
|
+ private void assertStringValue(final String expected) {
|
|
|
+ Assertions.assertThat(flagSet.toString())
|
|
|
+ .isEqualTo(expected);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Assert the configuration string form matches that expected.
|
|
|
+ */
|
|
|
+ public void assertConfigurationStringMatches(final String expected) {
|
|
|
+ Assertions.assertThat(flagSet.toConfigurationString())
|
|
|
+ .describedAs("Configuration string of %s", flagSet)
|
|
|
+ .isEqualTo(expected);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Test parsing from a configuration file.
|
|
|
+ * Multiple entries must be parsed, whitespace trimmed.
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testConfEntry() {
|
|
|
+ flagSet = flagSetFromConfig("a\t,\nc ", true);
|
|
|
+ assertFlagSetMatches(flagSet, SimpleEnum.a, SimpleEnum.c);
|
|
|
+ assertHasCapability(CAPABILITY_A);
|
|
|
+ assertHasCapability(CAPABILITY_C);
|
|
|
+ assertLacksCapability(CAPABILITY_B);
|
|
|
+ assertPathCapabilitiesMatch(flagSet, CAPABILITY_A, CAPABILITY_C);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Create a flagset from a configuration string.
|
|
|
+ * @param string configuration string.
|
|
|
+ * @param ignoreUnknown should unknown values be ignored?
|
|
|
+ * @return a flagset
|
|
|
+ */
|
|
|
+ private static FlagSet<SimpleEnum> flagSetFromConfig(final String string,
|
|
|
+ final boolean ignoreUnknown) {
|
|
|
+ final Configuration conf = mkConf(string);
|
|
|
+ return buildFlagSet(SimpleEnum.class, conf, KEY, ignoreUnknown);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Test parsing from a configuration file,
|
|
|
+ * where an entry is unknown; the builder is set to ignoreUnknown.
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testConfEntryWithUnknownIgnored() {
|
|
|
+ flagSet = flagSetFromConfig("a, unknown", true);
|
|
|
+ assertFlagSetMatches(flagSet, SimpleEnum.a);
|
|
|
+ assertHasCapability(CAPABILITY_A);
|
|
|
+ assertLacksCapability(CAPABILITY_B);
|
|
|
+ assertLacksCapability(CAPABILITY_C);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Test parsing from a configuration file where
|
|
|
+ * the same entry is duplicated.
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testDuplicateConfEntry() {
|
|
|
+ flagSet = flagSetFromConfig("a,\ta,\na\"", true);
|
|
|
+ assertFlagSetMatches(flagSet, SimpleEnum.a);
|
|
|
+ assertHasCapability(CAPABILITY_A);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Handle an unknown configuration value.
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testConfUnknownFailure() throws Throwable {
|
|
|
+ intercept(IllegalArgumentException.class, () ->
|
|
|
+ flagSetFromConfig("a, unknown", false));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Create a configuration with {@link #KEY} set to the given value.
|
|
|
+ * @param value value to set
|
|
|
+ * @return the configuration.
|
|
|
+ */
|
|
|
+ private static Configuration mkConf(final String value) {
|
|
|
+ final Configuration conf = new Configuration(false);
|
|
|
+ conf.set(KEY, value);
|
|
|
+ return conf;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Assert that the flagset has a capability.
|
|
|
+ * @param capability capability to probe for
|
|
|
+ */
|
|
|
+ private void assertHasCapability(final String capability) {
|
|
|
+ Assertions.assertThat(flagSet.hasCapability(capability))
|
|
|
+ .describedAs("Capability of %s on %s", capability, flagSet)
|
|
|
+ .isTrue();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Assert that the flagset lacks a capability.
|
|
|
+ * @param capability capability to probe for
|
|
|
+ */
|
|
|
+ private void assertLacksCapability(final String capability) {
|
|
|
+ Assertions.assertThat(flagSet.hasCapability(capability))
|
|
|
+ .describedAs("Capability of %s on %s", capability, flagSet)
|
|
|
+ .isFalse();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Test the * binding.
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testStarEntry() {
|
|
|
+ flagSet = flagSetFromConfig("*", false);
|
|
|
+ assertFlags(SimpleEnum.a, SimpleEnum.b, SimpleEnum.c);
|
|
|
+ assertHasCapability(CAPABILITY_A);
|
|
|
+ assertHasCapability(CAPABILITY_B);
|
|
|
+ Assertions.assertThat(flagSet.pathCapabilities())
|
|
|
+ .describedAs("path capabilities of %s", flagSet)
|
|
|
+ .containsExactlyInAnyOrder(CAPABILITY_A, CAPABILITY_B, CAPABILITY_C);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testRoundTrip() {
|
|
|
+ final FlagSet<SimpleEnum> s1 = createFlagSet(SimpleEnum.class,
|
|
|
+ KEYDOT,
|
|
|
+ allOf(SimpleEnum.class));
|
|
|
+ final FlagSet<SimpleEnum> s2 = roundTrip(s1);
|
|
|
+ Assertions.assertThat(s1.flags()).isEqualTo(s2.flags());
|
|
|
+ assertFlagSetMatches(s2, SimpleEnum.a, SimpleEnum.b, SimpleEnum.c);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testEmptyRoundTrip() {
|
|
|
+ final FlagSet<SimpleEnum> s1 = createFlagSet(SimpleEnum.class, KEYDOT,
|
|
|
+ noneOf(SimpleEnum.class));
|
|
|
+ final FlagSet<SimpleEnum> s2 = roundTrip(s1);
|
|
|
+ Assertions.assertThat(s1.flags())
|
|
|
+ .isEqualTo(s2.flags());
|
|
|
+ Assertions.assertThat(s2.isEmpty())
|
|
|
+ .describedAs("empty flagset %s", s2)
|
|
|
+ .isTrue();
|
|
|
+ assertFlagSetMatches(flagSet);
|
|
|
+ Assertions.assertThat(flagSet.pathCapabilities())
|
|
|
+ .describedAs("path capabilities of %s", flagSet)
|
|
|
+ .isEmpty();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testSetIsClone() {
|
|
|
+ final EnumSet<SimpleEnum> flags = noneOf(SimpleEnum.class);
|
|
|
+ final FlagSet<SimpleEnum> s1 = createFlagSet(SimpleEnum.class, KEYDOT, flags);
|
|
|
+ s1.enable(SimpleEnum.b);
|
|
|
+
|
|
|
+ // set a source flag
|
|
|
+ flags.add(SimpleEnum.a);
|
|
|
+
|
|
|
+ // verify the derived flagset is unchanged
|
|
|
+ assertFlagSetMatches(s1, SimpleEnum.b);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testEquality() {
|
|
|
+ final FlagSet<SimpleEnum> s1 = createFlagSet(SimpleEnum.class, KEYDOT, SimpleEnum.a);
|
|
|
+ final FlagSet<SimpleEnum> s2 = createFlagSet(SimpleEnum.class, KEYDOT, SimpleEnum.a);
|
|
|
+ // make one of them immutable
|
|
|
+ s2.makeImmutable();
|
|
|
+ Assertions.assertThat(s1)
|
|
|
+ .describedAs("s1 == s2")
|
|
|
+ .isEqualTo(s2);
|
|
|
+ Assertions.assertThat(s1.hashCode())
|
|
|
+ .describedAs("hashcode of s1 == hashcode of s2")
|
|
|
+ .isEqualTo(s2.hashCode());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testInequality() {
|
|
|
+ final FlagSet<SimpleEnum> s1 =
|
|
|
+ createFlagSet(SimpleEnum.class, KEYDOT, noneOf(SimpleEnum.class));
|
|
|
+ final FlagSet<SimpleEnum> s2 =
|
|
|
+ createFlagSet(SimpleEnum.class, KEYDOT, SimpleEnum.a, SimpleEnum.b);
|
|
|
+ Assertions.assertThat(s1)
|
|
|
+ .describedAs("s1 == s2")
|
|
|
+ .isNotEqualTo(s2);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testClassInequality() {
|
|
|
+ final FlagSet<?> s1 =
|
|
|
+ createFlagSet(SimpleEnum.class, KEYDOT, noneOf(SimpleEnum.class));
|
|
|
+ final FlagSet<?> s2 =
|
|
|
+ createFlagSet(OtherEnum.class, KEYDOT, OtherEnum.a);
|
|
|
+ Assertions.assertThat(s1)
|
|
|
+ .describedAs("s1 == s2")
|
|
|
+ .isNotEqualTo(s2);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The copy operation creates a new instance which is now mutable,
|
|
|
+ * even if the original was immutable.
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testCopy() throws Throwable {
|
|
|
+ FlagSet<SimpleEnum> s1 =
|
|
|
+ createFlagSet(SimpleEnum.class, KEYDOT, SimpleEnum.a, SimpleEnum.b);
|
|
|
+ s1.makeImmutable();
|
|
|
+ FlagSet<SimpleEnum> s2 = s1.copy();
|
|
|
+ Assertions.assertThat(s2)
|
|
|
+ .describedAs("copy of %s", s1)
|
|
|
+ .isNotSameAs(s1);
|
|
|
+ Assertions.assertThat(!s2.isImmutable())
|
|
|
+ .describedAs("set %s is immutable", s2)
|
|
|
+ .isTrue();
|
|
|
+ Assertions.assertThat(s1)
|
|
|
+ .describedAs("s1 == s2")
|
|
|
+ .isEqualTo(s2);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testCreateNullEnumClass() throws Throwable {
|
|
|
+ intercept(NullPointerException.class, () ->
|
|
|
+ createFlagSet(null, KEYDOT, SimpleEnum.a));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testCreateNullPrefix() throws Throwable {
|
|
|
+ intercept(NullPointerException.class, () ->
|
|
|
+ createFlagSet(SimpleEnum.class, null, SimpleEnum.a));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Round trip a FlagSet.
|
|
|
+ * @param flagset FlagSet to save to a configuration and retrieve.
|
|
|
+ * @return a new FlagSet.
|
|
|
+ */
|
|
|
+ private FlagSet<SimpleEnum> roundTrip(FlagSet<SimpleEnum> flagset) {
|
|
|
+ final Configuration conf = new Configuration(false);
|
|
|
+ conf.set(KEY, flagset.toConfigurationString());
|
|
|
+ return buildFlagSet(SimpleEnum.class, conf, KEY, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Assert a flag is enabled in the {@link #flagSet} field.
|
|
|
+ * @param flag flag to check
|
|
|
+ */
|
|
|
+ private void assertEnabled(final SimpleEnum flag) {
|
|
|
+ Assertions.assertThat(flagSet.enabled(flag))
|
|
|
+ .describedAs("status of flag %s in %s", flag, flagSet)
|
|
|
+ .isTrue();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Assert a flag is disabled in the {@link #flagSet} field.
|
|
|
+ * @param flag flag to check
|
|
|
+ */
|
|
|
+ private void assertDisabled(final SimpleEnum flag) {
|
|
|
+ Assertions.assertThat(flagSet.enabled(flag))
|
|
|
+ .describedAs("status of flag %s in %s", flag, flagSet)
|
|
|
+ .isFalse();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Assert that a set of flags are enabled in the {@link #flagSet} field.
|
|
|
+ * @param flags flags which must be set.
|
|
|
+ */
|
|
|
+ private void assertFlags(final SimpleEnum... flags) {
|
|
|
+ for (SimpleEnum flag : flags) {
|
|
|
+ assertEnabled(flag);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Assert that a FlagSet contains an exclusive set of values.
|
|
|
+ * @param flags flags which must be set.
|
|
|
+ */
|
|
|
+ private void assertFlagSetMatches(
|
|
|
+ FlagSet<SimpleEnum> fs,
|
|
|
+ SimpleEnum... flags) {
|
|
|
+ Assertions.assertThat(fs.flags())
|
|
|
+ .describedAs("path capabilities of %s", fs)
|
|
|
+ .containsExactly(flags);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Assert that a flagset contains exactly the capabilities.
|
|
|
+ * This is calculated by getting the list of active capabilities
|
|
|
+ * and asserting on the list.
|
|
|
+ * @param fs flagset
|
|
|
+ * @param capabilities capabilities
|
|
|
+ */
|
|
|
+ private void assertPathCapabilitiesMatch(
|
|
|
+ FlagSet<SimpleEnum> fs,
|
|
|
+ String... capabilities) {
|
|
|
+ Assertions.assertThat(fs.pathCapabilities())
|
|
|
+ .describedAs("path capabilities of %s", fs)
|
|
|
+ .containsExactlyInAnyOrder(capabilities);
|
|
|
+ }
|
|
|
+}
|