TestFlagSet.java
/*
* 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);
}
}