Showing
23 changed files
with
1348 additions
and
0 deletions
1 | +package org.onlab.onos.net.intent; | ||
2 | +//TODO is this the right package? | ||
3 | + | ||
4 | +/** | ||
5 | + * A generalized interface for ID generation | ||
6 | + * | ||
7 | + * {@link #getNewId()} generates a globally unique ID instance on | ||
8 | + * each invocation. | ||
9 | + * | ||
10 | + * @param <T> the type of ID | ||
11 | + */ | ||
12 | +// TODO: do we need to define a base marker interface for ID, | ||
13 | +// then changed the type parameter to <T extends BaseId> something | ||
14 | +// like that? | ||
15 | +public interface IdGenerator<T> { | ||
16 | + /** | ||
17 | + * Returns a globally unique ID instance. | ||
18 | + * | ||
19 | + * @return globally unique ID instance | ||
20 | + */ | ||
21 | + T getNewId(); | ||
22 | +} |
1 | +package org.onlab.onos.net.intent; | ||
2 | + | ||
3 | +import java.util.Set; | ||
4 | + | ||
5 | +import org.onlab.onos.net.ConnectPoint; | ||
6 | +import org.onlab.onos.net.DeviceId; | ||
7 | +import org.onlab.onos.net.PortNumber; | ||
8 | +import org.onlab.onos.net.flow.DefaultTrafficSelector; | ||
9 | +import org.onlab.onos.net.flow.DefaultTrafficTreatment; | ||
10 | +import org.onlab.onos.net.flow.TrafficSelector; | ||
11 | +import org.onlab.onos.net.flow.TrafficTreatment; | ||
12 | + | ||
13 | +/** | ||
14 | + * Base facilities to test various connectivity tests. | ||
15 | + */ | ||
16 | +public abstract class ConnectivityIntentTest extends IntentTest { | ||
17 | + | ||
18 | + public static final IntentId IID = new IntentId(123); | ||
19 | + public static final TrafficSelector MATCH = (new DefaultTrafficSelector.Builder()).build(); | ||
20 | + public static final TrafficTreatment NOP = (new DefaultTrafficTreatment.Builder()).build(); | ||
21 | + | ||
22 | + public static final ConnectPoint P1 = new ConnectPoint(DeviceId.deviceId("111"), PortNumber.portNumber(0x1)); | ||
23 | + public static final ConnectPoint P2 = new ConnectPoint(DeviceId.deviceId("222"), PortNumber.portNumber(0x2)); | ||
24 | + public static final ConnectPoint P3 = new ConnectPoint(DeviceId.deviceId("333"), PortNumber.portNumber(0x3)); | ||
25 | + | ||
26 | + public static final Set<ConnectPoint> PS1 = itemSet(new ConnectPoint[]{P1, P3}); | ||
27 | + public static final Set<ConnectPoint> PS2 = itemSet(new ConnectPoint[]{P2, P3}); | ||
28 | +} |
1 | +package org.onlab.onos.net.intent; | ||
2 | + | ||
3 | +import java.util.ArrayList; | ||
4 | +import java.util.Collections; | ||
5 | +import java.util.HashMap; | ||
6 | +import java.util.HashSet; | ||
7 | +import java.util.List; | ||
8 | +import java.util.Map; | ||
9 | +import java.util.Set; | ||
10 | +import java.util.concurrent.ExecutorService; | ||
11 | +import java.util.concurrent.Executors; | ||
12 | + | ||
13 | +/** | ||
14 | + * Fake implementation of the intent service to assist in developing tests | ||
15 | + * of the interface contract. | ||
16 | + */ | ||
17 | +public class FakeIntentManager implements TestableIntentService { | ||
18 | + | ||
19 | + private final Map<IntentId, Intent> intents = new HashMap<>(); | ||
20 | + private final Map<IntentId, IntentState> intentStates = new HashMap<>(); | ||
21 | + private final Map<IntentId, List<InstallableIntent>> installables = new HashMap<>(); | ||
22 | + private final Set<IntentEventListener> listeners = new HashSet<>(); | ||
23 | + | ||
24 | + private final Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> compilers = new HashMap<>(); | ||
25 | + private final Map<Class<? extends InstallableIntent>, | ||
26 | + IntentInstaller<? extends InstallableIntent>> installers = new HashMap<>(); | ||
27 | + | ||
28 | + private final ExecutorService executor = Executors.newSingleThreadExecutor(); | ||
29 | + private final List<IntentException> exceptions = new ArrayList<>(); | ||
30 | + | ||
31 | + @Override | ||
32 | + public List<IntentException> getExceptions() { | ||
33 | + return exceptions; | ||
34 | + } | ||
35 | + | ||
36 | + // Provides an out-of-thread simulation of intent submit life-cycle | ||
37 | + private void executeSubmit(final Intent intent) { | ||
38 | + registerSubclassCompilerIfNeeded(intent); | ||
39 | + executor.execute(new Runnable() { | ||
40 | + @Override | ||
41 | + public void run() { | ||
42 | + try { | ||
43 | + List<InstallableIntent> installable = compileIntent(intent); | ||
44 | + installIntents(intent, installable); | ||
45 | + } catch (IntentException e) { | ||
46 | + exceptions.add(e); | ||
47 | + } | ||
48 | + } | ||
49 | + }); | ||
50 | + } | ||
51 | + | ||
52 | + // Provides an out-of-thread simulation of intent withdraw life-cycle | ||
53 | + private void executeWithdraw(final Intent intent) { | ||
54 | + executor.execute(new Runnable() { | ||
55 | + @Override | ||
56 | + public void run() { | ||
57 | + try { | ||
58 | + List<InstallableIntent> installable = getInstallable(intent.getId()); | ||
59 | + uninstallIntents(intent, installable); | ||
60 | + } catch (IntentException e) { | ||
61 | + exceptions.add(e); | ||
62 | + } | ||
63 | + | ||
64 | + } | ||
65 | + }); | ||
66 | + } | ||
67 | + | ||
68 | + private <T extends Intent> IntentCompiler<T> getCompiler(T intent) { | ||
69 | + @SuppressWarnings("unchecked") | ||
70 | + IntentCompiler<T> compiler = (IntentCompiler<T>) compilers.get(intent.getClass()); | ||
71 | + if (compiler == null) { | ||
72 | + throw new IntentException("no compiler for class " + intent.getClass()); | ||
73 | + } | ||
74 | + return compiler; | ||
75 | + } | ||
76 | + | ||
77 | + private <T extends InstallableIntent> IntentInstaller<T> getInstaller(T intent) { | ||
78 | + @SuppressWarnings("unchecked") | ||
79 | + IntentInstaller<T> installer = (IntentInstaller<T>) installers.get(intent.getClass()); | ||
80 | + if (installer == null) { | ||
81 | + throw new IntentException("no installer for class " + intent.getClass()); | ||
82 | + } | ||
83 | + return installer; | ||
84 | + } | ||
85 | + | ||
86 | + private <T extends Intent> List<InstallableIntent> compileIntent(T intent) { | ||
87 | + try { | ||
88 | + // For the fake, we compile using a single level pass | ||
89 | + List<InstallableIntent> installable = new ArrayList<>(); | ||
90 | + for (Intent compiled : getCompiler(intent).compile(intent)) { | ||
91 | + installable.add((InstallableIntent) compiled); | ||
92 | + } | ||
93 | + setState(intent, IntentState.COMPILED); | ||
94 | + return installable; | ||
95 | + } catch (IntentException e) { | ||
96 | + setState(intent, IntentState.FAILED); | ||
97 | + throw e; | ||
98 | + } | ||
99 | + } | ||
100 | + | ||
101 | + private void installIntents(Intent intent, List<InstallableIntent> installable) { | ||
102 | + try { | ||
103 | + for (InstallableIntent ii : installable) { | ||
104 | + registerSubclassInstallerIfNeeded(ii); | ||
105 | + getInstaller(ii).install(ii); | ||
106 | + } | ||
107 | + setState(intent, IntentState.INSTALLED); | ||
108 | + putInstallable(intent.getId(), installable); | ||
109 | + } catch (IntentException e) { | ||
110 | + setState(intent, IntentState.FAILED); | ||
111 | + throw e; | ||
112 | + } | ||
113 | + } | ||
114 | + | ||
115 | + private void uninstallIntents(Intent intent, List<InstallableIntent> installable) { | ||
116 | + try { | ||
117 | + for (InstallableIntent ii : installable) { | ||
118 | + getInstaller(ii).uninstall(ii); | ||
119 | + } | ||
120 | + setState(intent, IntentState.WITHDRAWN); | ||
121 | + removeInstallable(intent.getId()); | ||
122 | + } catch (IntentException e) { | ||
123 | + setState(intent, IntentState.FAILED); | ||
124 | + throw e; | ||
125 | + } | ||
126 | + } | ||
127 | + | ||
128 | + | ||
129 | + // Sets the internal state for the given intent and dispatches an event | ||
130 | + private void setState(Intent intent, IntentState state) { | ||
131 | + IntentState previous = intentStates.get(intent.getId()); | ||
132 | + intentStates.put(intent.getId(), state); | ||
133 | + dispatch(new IntentEvent(intent, state, previous, System.currentTimeMillis())); | ||
134 | + } | ||
135 | + | ||
136 | + private void putInstallable(IntentId id, List<InstallableIntent> installable) { | ||
137 | + installables.put(id, installable); | ||
138 | + } | ||
139 | + | ||
140 | + private void removeInstallable(IntentId id) { | ||
141 | + installables.remove(id); | ||
142 | + } | ||
143 | + | ||
144 | + private List<InstallableIntent> getInstallable(IntentId id) { | ||
145 | + List<InstallableIntent> installable = installables.get(id); | ||
146 | + if (installable != null) { | ||
147 | + return installable; | ||
148 | + } else { | ||
149 | + return Collections.emptyList(); | ||
150 | + } | ||
151 | + } | ||
152 | + | ||
153 | + @Override | ||
154 | + public void submit(Intent intent) { | ||
155 | + intents.put(intent.getId(), intent); | ||
156 | + setState(intent, IntentState.SUBMITTED); | ||
157 | + executeSubmit(intent); | ||
158 | + } | ||
159 | + | ||
160 | + @Override | ||
161 | + public void withdraw(Intent intent) { | ||
162 | + intents.remove(intent.getId()); | ||
163 | + setState(intent, IntentState.WITHDRAWING); | ||
164 | + executeWithdraw(intent); | ||
165 | + } | ||
166 | + | ||
167 | + @Override | ||
168 | + public void execute(IntentOperations operations) { | ||
169 | + // TODO: implement later | ||
170 | + } | ||
171 | + | ||
172 | + @Override | ||
173 | + public Set<Intent> getIntents() { | ||
174 | + return Collections.unmodifiableSet(new HashSet<>(intents.values())); | ||
175 | + } | ||
176 | + | ||
177 | + @Override | ||
178 | + public Intent getIntent(IntentId id) { | ||
179 | + return intents.get(id); | ||
180 | + } | ||
181 | + | ||
182 | + @Override | ||
183 | + public IntentState getIntentState(IntentId id) { | ||
184 | + return intentStates.get(id); | ||
185 | + } | ||
186 | + | ||
187 | + @Override | ||
188 | + public void addListener(IntentEventListener listener) { | ||
189 | + listeners.add(listener); | ||
190 | + } | ||
191 | + | ||
192 | + @Override | ||
193 | + public void removeListener(IntentEventListener listener) { | ||
194 | + listeners.remove(listener); | ||
195 | + } | ||
196 | + | ||
197 | + private void dispatch(IntentEvent event) { | ||
198 | + for (IntentEventListener listener : listeners) { | ||
199 | + listener.event(event); | ||
200 | + } | ||
201 | + } | ||
202 | + | ||
203 | + @Override | ||
204 | + public <T extends Intent> void registerCompiler(Class<T> cls, IntentCompiler<T> compiler) { | ||
205 | + compilers.put(cls, compiler); | ||
206 | + } | ||
207 | + | ||
208 | + @Override | ||
209 | + public <T extends Intent> void unregisterCompiler(Class<T> cls) { | ||
210 | + compilers.remove(cls); | ||
211 | + } | ||
212 | + | ||
213 | + @Override | ||
214 | + public Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> getCompilers() { | ||
215 | + return Collections.unmodifiableMap(compilers); | ||
216 | + } | ||
217 | + | ||
218 | + @Override | ||
219 | + public <T extends InstallableIntent> void registerInstaller(Class<T> cls, IntentInstaller<T> installer) { | ||
220 | + installers.put(cls, installer); | ||
221 | + } | ||
222 | + | ||
223 | + @Override | ||
224 | + public <T extends InstallableIntent> void unregisterInstaller(Class<T> cls) { | ||
225 | + installers.remove(cls); | ||
226 | + } | ||
227 | + | ||
228 | + @Override | ||
229 | + public Map<Class<? extends InstallableIntent>, | ||
230 | + IntentInstaller<? extends InstallableIntent>> getInstallers() { | ||
231 | + return Collections.unmodifiableMap(installers); | ||
232 | + } | ||
233 | + | ||
234 | + private void registerSubclassCompilerIfNeeded(Intent intent) { | ||
235 | + if (!compilers.containsKey(intent.getClass())) { | ||
236 | + Class<?> cls = intent.getClass(); | ||
237 | + while (cls != Object.class) { | ||
238 | + // As long as we're within the Intent class descendants | ||
239 | + if (Intent.class.isAssignableFrom(cls)) { | ||
240 | + IntentCompiler<?> compiler = compilers.get(cls); | ||
241 | + if (compiler != null) { | ||
242 | + compilers.put(intent.getClass(), compiler); | ||
243 | + return; | ||
244 | + } | ||
245 | + } | ||
246 | + cls = cls.getSuperclass(); | ||
247 | + } | ||
248 | + } | ||
249 | + } | ||
250 | + | ||
251 | + private void registerSubclassInstallerIfNeeded(InstallableIntent intent) { | ||
252 | + if (!installers.containsKey(intent.getClass())) { | ||
253 | + Class<?> cls = intent.getClass(); | ||
254 | + while (cls != Object.class) { | ||
255 | + // As long as we're within the InstallableIntent class descendants | ||
256 | + if (InstallableIntent.class.isAssignableFrom(cls)) { | ||
257 | + IntentInstaller<?> installer = installers.get(cls); | ||
258 | + if (installer != null) { | ||
259 | + installers.put(intent.getClass(), installer); | ||
260 | + return; | ||
261 | + } | ||
262 | + } | ||
263 | + cls = cls.getSuperclass(); | ||
264 | + } | ||
265 | + } | ||
266 | + } | ||
267 | + | ||
268 | +} |
1 | +package org.onlab.onos.net.intent; | ||
2 | +//TODO is this the right package? | ||
3 | + | ||
4 | +import org.hamcrest.Description; | ||
5 | +import org.hamcrest.StringDescription; | ||
6 | + | ||
7 | +import java.lang.reflect.Field; | ||
8 | +import java.lang.reflect.Method; | ||
9 | +import java.lang.reflect.Modifier; | ||
10 | + | ||
11 | +/** | ||
12 | + * Hamcrest style class for verifying that a class follows the | ||
13 | + * accepted rules for immutable classes. | ||
14 | + * | ||
15 | + * The rules that are enforced for immutable classes: | ||
16 | + * - the class must be declared final | ||
17 | + * - all data members of the class must be declared private and final | ||
18 | + * - the class must not define any setter methods | ||
19 | + */ | ||
20 | + | ||
21 | +public class ImmutableClassChecker { | ||
22 | + | ||
23 | + private String failureReason = ""; | ||
24 | + | ||
25 | + /** | ||
26 | + * Method to determine if a given class is a properly specified | ||
27 | + * immutable class. | ||
28 | + * | ||
29 | + * @param clazz the class to check | ||
30 | + * @return true if the given class is a properly specified immutable class. | ||
31 | + */ | ||
32 | + private boolean isImmutableClass(Class<?> clazz) { | ||
33 | + // class must be declared final | ||
34 | + if (!Modifier.isFinal(clazz.getModifiers())) { | ||
35 | + failureReason = "a class that is not final"; | ||
36 | + return false; | ||
37 | + } | ||
38 | + | ||
39 | + // class must have only final and private data members | ||
40 | + for (final Field field : clazz.getDeclaredFields()) { | ||
41 | + if (field.getName().startsWith("__cobertura")) { | ||
42 | + // cobertura sticks these fields into classes - ignore them | ||
43 | + continue; | ||
44 | + } | ||
45 | + if (!Modifier.isFinal(field.getModifiers())) { | ||
46 | + failureReason = "a field named '" + field.getName() + | ||
47 | + "' that is not final"; | ||
48 | + return false; | ||
49 | + } | ||
50 | + if (!Modifier.isPrivate(field.getModifiers())) { | ||
51 | + // | ||
52 | + // NOTE: We relax the recommended rules for defining immutable | ||
53 | + // objects and allow "static final" fields that are not | ||
54 | + // private. The "final" check was already done above so we | ||
55 | + // don't repeat it here. | ||
56 | + // | ||
57 | + if (!Modifier.isStatic(field.getModifiers())) { | ||
58 | + failureReason = "a field named '" + field.getName() + | ||
59 | + "' that is not private and is not static"; | ||
60 | + return false; | ||
61 | + } | ||
62 | + } | ||
63 | + } | ||
64 | + | ||
65 | + // class must not define any setters | ||
66 | + for (final Method method : clazz.getMethods()) { | ||
67 | + if (method.getDeclaringClass().equals(clazz)) { | ||
68 | + if (method.getName().startsWith("set")) { | ||
69 | + failureReason = "a class with a setter named '" + method.getName() + "'"; | ||
70 | + return false; | ||
71 | + } | ||
72 | + } | ||
73 | + } | ||
74 | + | ||
75 | + return true; | ||
76 | + } | ||
77 | + | ||
78 | + /** | ||
79 | + * Describe why an error was reported. Uses Hamcrest style Description | ||
80 | + * interfaces. | ||
81 | + * | ||
82 | + * @param description the Description object to use for reporting the | ||
83 | + * mismatch | ||
84 | + */ | ||
85 | + public void describeMismatch(Description description) { | ||
86 | + description.appendText(failureReason); | ||
87 | + } | ||
88 | + | ||
89 | + /** | ||
90 | + * Describe the source object that caused an error, using a Hamcrest | ||
91 | + * Matcher style interface. In this case, it always returns | ||
92 | + * that we are looking for a properly defined utility class. | ||
93 | + * | ||
94 | + * @param description the Description object to use to report the "to" | ||
95 | + * object | ||
96 | + */ | ||
97 | + public void describeTo(Description description) { | ||
98 | + description.appendText("a properly defined immutable class"); | ||
99 | + } | ||
100 | + | ||
101 | + /** | ||
102 | + * Assert that the given class adheres to the utility class rules. | ||
103 | + * | ||
104 | + * @param clazz the class to check | ||
105 | + * | ||
106 | + * @throws java.lang.AssertionError if the class is not a valid | ||
107 | + * utility class | ||
108 | + */ | ||
109 | + public static void assertThatClassIsImmutable(Class<?> clazz) { | ||
110 | + final ImmutableClassChecker checker = new ImmutableClassChecker(); | ||
111 | + if (!checker.isImmutableClass(clazz)) { | ||
112 | + final Description toDescription = new StringDescription(); | ||
113 | + final Description mismatchDescription = new StringDescription(); | ||
114 | + | ||
115 | + checker.describeTo(toDescription); | ||
116 | + checker.describeMismatch(mismatchDescription); | ||
117 | + final String reason = | ||
118 | + "\n" + | ||
119 | + "Expected: is \"" + toDescription.toString() + "\"\n" + | ||
120 | + " but : was \"" + mismatchDescription.toString() + "\""; | ||
121 | + | ||
122 | + throw new AssertionError(reason); | ||
123 | + } | ||
124 | + } | ||
125 | +} |
1 | +package org.onlab.onos.net.intent; | ||
2 | + | ||
3 | +import org.junit.Test; | ||
4 | + | ||
5 | +import static org.junit.Assert.assertEquals; | ||
6 | + | ||
7 | +/** | ||
8 | + * Test of the intent exception. | ||
9 | + */ | ||
10 | +public class IntentExceptionTest { | ||
11 | + | ||
12 | + @Test | ||
13 | + public void basics() { | ||
14 | + validate(new IntentException(), null, null); | ||
15 | + validate(new IntentException("foo"), "foo", null); | ||
16 | + | ||
17 | + Throwable cause = new NullPointerException("bar"); | ||
18 | + validate(new IntentException("foo", cause), "foo", cause); | ||
19 | + } | ||
20 | + | ||
21 | + /** | ||
22 | + * Validates that the specified exception has the correct message and cause. | ||
23 | + * | ||
24 | + * @param e exception to test | ||
25 | + * @param message expected message | ||
26 | + * @param cause expected cause | ||
27 | + */ | ||
28 | + protected void validate(RuntimeException e, String message, Throwable cause) { | ||
29 | + assertEquals("incorrect message", message, e.getMessage()); | ||
30 | + assertEquals("incorrect cause", cause, e.getCause()); | ||
31 | + } | ||
32 | + | ||
33 | +} |
1 | +package org.onlab.onos.net.intent; | ||
2 | + | ||
3 | +/** | ||
4 | + * This interface is for generator of IntentId. It is defined only for | ||
5 | + * testing purpose to keep type safety on mock creation. | ||
6 | + * | ||
7 | + * <p> | ||
8 | + * {@link #getNewId()} generates a globally unique {@link IntentId} instance | ||
9 | + * on each invocation. Application developers should not generate IntentId | ||
10 | + * by themselves. Instead use an implementation of this interface. | ||
11 | + * </p> | ||
12 | + */ | ||
13 | +public interface IntentIdGenerator extends IdGenerator<IntentId> { | ||
14 | +} |
1 | +package org.onlab.onos.net.intent; | ||
2 | + | ||
3 | +import org.junit.Test; | ||
4 | + | ||
5 | +import static org.hamcrest.Matchers.is; | ||
6 | +import static org.hamcrest.Matchers.not; | ||
7 | +import static org.junit.Assert.assertEquals; | ||
8 | +import static org.junit.Assert.assertThat; | ||
9 | + | ||
10 | +/** | ||
11 | + * This class tests the immutability, equality, and non-equality of | ||
12 | + * {@link IntentId}. | ||
13 | + */ | ||
14 | +public class IntentIdTest { | ||
15 | + /** | ||
16 | + * Tests the immutability of {@link IntentId}. | ||
17 | + */ | ||
18 | + @Test | ||
19 | + public void intentIdFollowsGuidelineForImmutableObject() { | ||
20 | + ImmutableClassChecker.assertThatClassIsImmutable(IntentId.class); | ||
21 | + } | ||
22 | + | ||
23 | + /** | ||
24 | + * Tests equality of {@link IntentId}. | ||
25 | + */ | ||
26 | + @Test | ||
27 | + public void testEquality() { | ||
28 | + IntentId id1 = new IntentId(1L); | ||
29 | + IntentId id2 = new IntentId(1L); | ||
30 | + | ||
31 | + assertThat(id1, is(id2)); | ||
32 | + } | ||
33 | + | ||
34 | + /** | ||
35 | + * Tests non-equality of {@link IntentId}. | ||
36 | + */ | ||
37 | + @Test | ||
38 | + public void testNonEquality() { | ||
39 | + IntentId id1 = new IntentId(1L); | ||
40 | + IntentId id2 = new IntentId(2L); | ||
41 | + | ||
42 | + assertThat(id1, is(not(id2))); | ||
43 | + } | ||
44 | + | ||
45 | + @Test | ||
46 | + public void valueOf() { | ||
47 | + IntentId id = new IntentId(12345); | ||
48 | + assertEquals("incorrect valueOf", id, IntentId.valueOf("12345")); | ||
49 | + } | ||
50 | + | ||
51 | + @Test | ||
52 | + public void valueOfHex() { | ||
53 | + IntentId id = new IntentId(0xdeadbeefL); | ||
54 | + assertEquals("incorrect valueOf", id, IntentId.valueOf(id.toString())); | ||
55 | + } | ||
56 | + | ||
57 | +} |
1 | +package org.onlab.onos.net.intent; | ||
2 | + | ||
3 | +import org.junit.After; | ||
4 | +import org.junit.Before; | ||
5 | +import org.junit.Test; | ||
6 | + | ||
7 | +import java.util.ArrayList; | ||
8 | +import java.util.Arrays; | ||
9 | +import java.util.Collections; | ||
10 | +import java.util.Iterator; | ||
11 | +import java.util.List; | ||
12 | + | ||
13 | +import static org.onlab.onos.net.intent.IntentState.*; | ||
14 | +import static org.junit.Assert.*; | ||
15 | + | ||
16 | +// TODO: consider make it categorized as integration test when it become | ||
17 | +// slow test or fragile test | ||
18 | +/** | ||
19 | + * Suite of tests for the intent service contract. | ||
20 | + */ | ||
21 | +public class IntentServiceTest { | ||
22 | + | ||
23 | + public static final IntentId IID = new IntentId(123); | ||
24 | + public static final IntentId INSTALLABLE_IID = new IntentId(234); | ||
25 | + | ||
26 | + protected static final int GRACE_MS = 500; // millis | ||
27 | + | ||
28 | + protected TestableIntentService service; | ||
29 | + protected TestListener listener = new TestListener(); | ||
30 | + | ||
31 | + @Before | ||
32 | + public void setUp() { | ||
33 | + service = createIntentService(); | ||
34 | + service.addListener(listener); | ||
35 | + } | ||
36 | + | ||
37 | + @After | ||
38 | + public void tearDown() { | ||
39 | + service.removeListener(listener); | ||
40 | + } | ||
41 | + | ||
42 | + /** | ||
43 | + * Creates a service instance appropriately instrumented for testing. | ||
44 | + * | ||
45 | + * @return testable intent service | ||
46 | + */ | ||
47 | + protected TestableIntentService createIntentService() { | ||
48 | + return new FakeIntentManager(); | ||
49 | + } | ||
50 | + | ||
51 | + @Test | ||
52 | + public void basics() { | ||
53 | + // Make sure there are no intents | ||
54 | + assertEquals("incorrect intent count", 0, service.getIntents().size()); | ||
55 | + | ||
56 | + // Register a compiler and an installer both setup for success. | ||
57 | + service.registerCompiler(TestIntent.class, new TestCompiler(new TestInstallableIntent(INSTALLABLE_IID))); | ||
58 | + service.registerInstaller(TestInstallableIntent.class, new TestInstaller(false)); | ||
59 | + | ||
60 | + final Intent intent = new TestIntent(IID); | ||
61 | + service.submit(intent); | ||
62 | + | ||
63 | + // Allow a small window of time until the intent is in the expected state | ||
64 | + TestTools.assertAfter(GRACE_MS, new Runnable() { | ||
65 | + @Override | ||
66 | + public void run() { | ||
67 | + assertEquals("incorrect intent state", INSTALLED, | ||
68 | + service.getIntentState(intent.getId())); | ||
69 | + } | ||
70 | + }); | ||
71 | + | ||
72 | + // Make sure that all expected events have been emitted | ||
73 | + validateEvents(intent, SUBMITTED, COMPILED, INSTALLED); | ||
74 | + | ||
75 | + // Make sure there is just one intent (and is ours) | ||
76 | + assertEquals("incorrect intent count", 1, service.getIntents().size()); | ||
77 | + assertEquals("incorrect intent", intent, service.getIntent(intent.getId())); | ||
78 | + | ||
79 | + // Reset the listener events | ||
80 | + listener.events.clear(); | ||
81 | + | ||
82 | + // Now withdraw the intent | ||
83 | + service.withdraw(intent); | ||
84 | + | ||
85 | + // Allow a small window of time until the event is in the expected state | ||
86 | + TestTools.assertAfter(GRACE_MS, new Runnable() { | ||
87 | + @Override | ||
88 | + public void run() { | ||
89 | + assertEquals("incorrect intent state", WITHDRAWN, | ||
90 | + service.getIntentState(intent.getId())); | ||
91 | + } | ||
92 | + }); | ||
93 | + | ||
94 | + // Make sure that all expected events have been emitted | ||
95 | + validateEvents(intent, WITHDRAWING, WITHDRAWN); | ||
96 | + | ||
97 | + // TODO: discuss what is the fate of intents after they have been withdrawn | ||
98 | + // Make sure that the intent is no longer in the system | ||
99 | +// assertEquals("incorrect intent count", 0, service.getIntents().size()); | ||
100 | +// assertNull("intent should not be found", service.getIntent(intent.getId())); | ||
101 | +// assertNull("intent state should not be found", service.getIntentState(intent.getId())); | ||
102 | + } | ||
103 | + | ||
104 | + @Test | ||
105 | + public void failedCompilation() { | ||
106 | + // Register a compiler programmed for success | ||
107 | + service.registerCompiler(TestIntent.class, new TestCompiler(true)); | ||
108 | + | ||
109 | + // Submit an intent | ||
110 | + final Intent intent = new TestIntent(IID); | ||
111 | + service.submit(intent); | ||
112 | + | ||
113 | + // Allow a small window of time until the intent is in the expected state | ||
114 | + TestTools.assertAfter(GRACE_MS, new Runnable() { | ||
115 | + @Override | ||
116 | + public void run() { | ||
117 | + assertEquals("incorrect intent state", FAILED, | ||
118 | + service.getIntentState(intent.getId())); | ||
119 | + } | ||
120 | + }); | ||
121 | + | ||
122 | + // Make sure that all expected events have been emitted | ||
123 | + validateEvents(intent, SUBMITTED, FAILED); | ||
124 | + } | ||
125 | + | ||
126 | + @Test | ||
127 | + public void failedInstallation() { | ||
128 | + // Register a compiler programmed for success and installer for failure | ||
129 | + service.registerCompiler(TestIntent.class, new TestCompiler(new TestInstallableIntent(INSTALLABLE_IID))); | ||
130 | + service.registerInstaller(TestInstallableIntent.class, new TestInstaller(true)); | ||
131 | + | ||
132 | + // Submit an intent | ||
133 | + final Intent intent = new TestIntent(IID); | ||
134 | + service.submit(intent); | ||
135 | + | ||
136 | + // Allow a small window of time until the intent is in the expected state | ||
137 | + TestTools.assertAfter(GRACE_MS, new Runnable() { | ||
138 | + @Override | ||
139 | + public void run() { | ||
140 | + assertEquals("incorrect intent state", FAILED, | ||
141 | + service.getIntentState(intent.getId())); | ||
142 | + } | ||
143 | + }); | ||
144 | + | ||
145 | + // Make sure that all expected events have been emitted | ||
146 | + validateEvents(intent, SUBMITTED, COMPILED, FAILED); | ||
147 | + } | ||
148 | + | ||
149 | + /** | ||
150 | + * Validates that the test event listener has received the following events | ||
151 | + * for the specified intent. Events received for other intents will not be | ||
152 | + * considered. | ||
153 | + * | ||
154 | + * @param intent intent subject | ||
155 | + * @param states list of states for which events are expected | ||
156 | + */ | ||
157 | + protected void validateEvents(Intent intent, IntentState... states) { | ||
158 | + Iterator<IntentEvent> events = listener.events.iterator(); | ||
159 | + for (IntentState state : states) { | ||
160 | + IntentEvent event = events.hasNext() ? events.next() : null; | ||
161 | + if (event == null) { | ||
162 | + fail("expected event not found: " + state); | ||
163 | + } else if (intent.equals(event.getIntent())) { | ||
164 | + assertEquals("incorrect state", state, event.getState()); | ||
165 | + } | ||
166 | + } | ||
167 | + | ||
168 | + // Remainder of events should not apply to this intent; make sure. | ||
169 | + while (events.hasNext()) { | ||
170 | + assertFalse("unexpected event for intent", | ||
171 | + intent.equals(events.next().getIntent())); | ||
172 | + } | ||
173 | + } | ||
174 | + | ||
175 | + @Test | ||
176 | + public void compilerBasics() { | ||
177 | + // Make sure there are no compilers | ||
178 | + assertEquals("incorrect compiler count", 0, service.getCompilers().size()); | ||
179 | + | ||
180 | + // Add a compiler and make sure that it appears in the map | ||
181 | + IntentCompiler<TestIntent> compiler = new TestCompiler(false); | ||
182 | + service.registerCompiler(TestIntent.class, compiler); | ||
183 | + assertEquals("incorrect compiler", compiler, | ||
184 | + service.getCompilers().get(TestIntent.class)); | ||
185 | + | ||
186 | + // Remove the same and make sure that it no longer appears in the map | ||
187 | + service.unregisterCompiler(TestIntent.class); | ||
188 | + assertNull("compiler should not be registered", | ||
189 | + service.getCompilers().get(TestIntent.class)); | ||
190 | + } | ||
191 | + | ||
192 | + @Test | ||
193 | + public void installerBasics() { | ||
194 | + // Make sure there are no installers | ||
195 | + assertEquals("incorrect installer count", 0, service.getInstallers().size()); | ||
196 | + | ||
197 | + // Add an installer and make sure that it appears in the map | ||
198 | + IntentInstaller<TestInstallableIntent> installer = new TestInstaller(false); | ||
199 | + service.registerInstaller(TestInstallableIntent.class, installer); | ||
200 | + assertEquals("incorrect installer", installer, | ||
201 | + service.getInstallers().get(TestInstallableIntent.class)); | ||
202 | + | ||
203 | + // Remove the same and make sure that it no longer appears in the map | ||
204 | + service.unregisterInstaller(TestInstallableIntent.class); | ||
205 | + assertNull("installer should not be registered", | ||
206 | + service.getInstallers().get(TestInstallableIntent.class)); | ||
207 | + } | ||
208 | + | ||
209 | + @Test | ||
210 | + public void implicitRegistration() { | ||
211 | + // Add a compiler and make sure that it appears in the map | ||
212 | + IntentCompiler<TestIntent> compiler = new TestCompiler(new TestSubclassInstallableIntent(INSTALLABLE_IID)); | ||
213 | + service.registerCompiler(TestIntent.class, compiler); | ||
214 | + assertEquals("incorrect compiler", compiler, | ||
215 | + service.getCompilers().get(TestIntent.class)); | ||
216 | + | ||
217 | + // Add a installer and make sure that it appears in the map | ||
218 | + IntentInstaller<TestInstallableIntent> installer = new TestInstaller(false); | ||
219 | + service.registerInstaller(TestInstallableIntent.class, installer); | ||
220 | + assertEquals("incorrect installer", installer, | ||
221 | + service.getInstallers().get(TestInstallableIntent.class)); | ||
222 | + | ||
223 | + | ||
224 | + // Submit an intent which is a subclass of the one we registered | ||
225 | + final Intent intent = new TestSubclassIntent(IID); | ||
226 | + service.submit(intent); | ||
227 | + | ||
228 | + // Allow some time for the intent to be compiled and installed | ||
229 | + TestTools.assertAfter(GRACE_MS, new Runnable() { | ||
230 | + @Override | ||
231 | + public void run() { | ||
232 | + assertEquals("incorrect intent state", INSTALLED, | ||
233 | + service.getIntentState(intent.getId())); | ||
234 | + } | ||
235 | + }); | ||
236 | + | ||
237 | + // Make sure that now we have an implicit registration of the compiler | ||
238 | + // under the intent subclass | ||
239 | + assertEquals("incorrect compiler", compiler, | ||
240 | + service.getCompilers().get(TestSubclassIntent.class)); | ||
241 | + | ||
242 | + // Make sure that now we have an implicit registration of the installer | ||
243 | + // under the intent subclass | ||
244 | + assertEquals("incorrect installer", installer, | ||
245 | + service.getInstallers().get(TestSubclassInstallableIntent.class)); | ||
246 | + | ||
247 | + // TODO: discuss whether or if implicit registration should require implicit unregistration | ||
248 | + // perhaps unregister by compiler or installer itself, rather than by class would be better | ||
249 | + } | ||
250 | + | ||
251 | + | ||
252 | + // Fixture to track emitted intent events | ||
253 | + protected class TestListener implements IntentEventListener { | ||
254 | + final List<IntentEvent> events = new ArrayList<>(); | ||
255 | + | ||
256 | + @Override | ||
257 | + public void event(IntentEvent event) { | ||
258 | + events.add(event); | ||
259 | + } | ||
260 | + } | ||
261 | + | ||
262 | + // Controllable compiler | ||
263 | + private class TestCompiler implements IntentCompiler<TestIntent> { | ||
264 | + private final boolean fail; | ||
265 | + private final List<Intent> result; | ||
266 | + | ||
267 | + TestCompiler(boolean fail) { | ||
268 | + this.fail = fail; | ||
269 | + this.result = Collections.emptyList(); | ||
270 | + } | ||
271 | + | ||
272 | + TestCompiler(Intent... result) { | ||
273 | + this.fail = false; | ||
274 | + this.result = Arrays.asList(result); | ||
275 | + } | ||
276 | + | ||
277 | + @Override | ||
278 | + public List<Intent> compile(TestIntent intent) { | ||
279 | + if (fail) { | ||
280 | + throw new IntentException("compile failed by design"); | ||
281 | + } | ||
282 | + List<Intent> compiled = new ArrayList<>(result); | ||
283 | + return compiled; | ||
284 | + } | ||
285 | + } | ||
286 | + | ||
287 | + // Controllable installer | ||
288 | + private class TestInstaller implements IntentInstaller<TestInstallableIntent> { | ||
289 | + private final boolean fail; | ||
290 | + | ||
291 | + TestInstaller(boolean fail) { | ||
292 | + this.fail = fail; | ||
293 | + } | ||
294 | + | ||
295 | + @Override | ||
296 | + public void install(TestInstallableIntent intent) { | ||
297 | + if (fail) { | ||
298 | + throw new IntentException("install failed by design"); | ||
299 | + } | ||
300 | + } | ||
301 | + | ||
302 | + @Override | ||
303 | + public void uninstall(TestInstallableIntent intent) { | ||
304 | + if (fail) { | ||
305 | + throw new IntentException("remove failed by design"); | ||
306 | + } | ||
307 | + } | ||
308 | + } | ||
309 | + | ||
310 | +} |
1 | +package org.onlab.onos.net.intent; | ||
2 | + | ||
3 | +import static org.junit.Assert.assertEquals; | ||
4 | +import static org.junit.Assert.assertFalse; | ||
5 | +import static org.junit.Assert.assertTrue; | ||
6 | + | ||
7 | +import java.util.Arrays; | ||
8 | +import java.util.HashSet; | ||
9 | +import java.util.Set; | ||
10 | + | ||
11 | +import org.junit.Test; | ||
12 | + | ||
13 | +/** | ||
14 | + * Base facilities to test various intent tests. | ||
15 | + */ | ||
16 | +public abstract class IntentTest { | ||
17 | + /** | ||
18 | + * Produces a set of items from the supplied items. | ||
19 | + * | ||
20 | + * @param items items to be placed in set | ||
21 | + * @param <T> item type | ||
22 | + * @return set of items | ||
23 | + */ | ||
24 | + protected static <T> Set<T> itemSet(T[] items) { | ||
25 | + return new HashSet<>(Arrays.asList(items)); | ||
26 | + } | ||
27 | + | ||
28 | + @Test | ||
29 | + public void equalsAndHashCode() { | ||
30 | + Intent one = createOne(); | ||
31 | + Intent like = createOne(); | ||
32 | + Intent another = createAnother(); | ||
33 | + | ||
34 | + assertTrue("should be equal", one.equals(like)); | ||
35 | + assertEquals("incorrect hashCode", one.hashCode(), like.hashCode()); | ||
36 | + | ||
37 | + assertFalse("should not be equal", one.equals(another)); | ||
38 | + | ||
39 | + assertFalse("should not be equal", one.equals(null)); | ||
40 | + assertFalse("should not be equal", one.equals("foo")); | ||
41 | + } | ||
42 | + | ||
43 | + @Test | ||
44 | + public void testToString() { | ||
45 | + Intent one = createOne(); | ||
46 | + Intent like = createOne(); | ||
47 | + assertEquals("incorrect toString", one.toString(), like.toString()); | ||
48 | + } | ||
49 | + | ||
50 | + /** | ||
51 | + * Creates a new intent, but always a like intent, i.e. all instances will | ||
52 | + * be equal, but should not be the same. | ||
53 | + * | ||
54 | + * @return intent | ||
55 | + */ | ||
56 | + protected abstract Intent createOne(); | ||
57 | + | ||
58 | + /** | ||
59 | + * Creates another intent, not equals to the one created by | ||
60 | + * {@link #createOne()} and with a different hash code. | ||
61 | + * | ||
62 | + * @return another intent | ||
63 | + */ | ||
64 | + protected abstract Intent createAnother(); | ||
65 | +} |
1 | +package org.onlab.onos.net.intent; | ||
2 | + | ||
3 | +import org.junit.Test; | ||
4 | + | ||
5 | +import static org.junit.Assert.assertEquals; | ||
6 | + | ||
7 | +/** | ||
8 | + * Suite of tests of the multi-to-single point intent descriptor. | ||
9 | + */ | ||
10 | +public class MultiPointToSinglePointIntentTest extends ConnectivityIntentTest { | ||
11 | + | ||
12 | + @Test | ||
13 | + public void basics() { | ||
14 | + MultiPointToSinglePointIntent intent = createOne(); | ||
15 | + assertEquals("incorrect id", IID, intent.getId()); | ||
16 | + assertEquals("incorrect match", MATCH, intent.getTrafficSelector()); | ||
17 | + assertEquals("incorrect ingress", PS1, intent.getIngressPorts()); | ||
18 | + assertEquals("incorrect egress", P2, intent.getEgressPort()); | ||
19 | + } | ||
20 | + | ||
21 | + @Override | ||
22 | + protected MultiPointToSinglePointIntent createOne() { | ||
23 | + return new MultiPointToSinglePointIntent(IID, MATCH, NOP, PS1, P2); | ||
24 | + } | ||
25 | + | ||
26 | + @Override | ||
27 | + protected MultiPointToSinglePointIntent createAnother() { | ||
28 | + return new MultiPointToSinglePointIntent(IID, MATCH, NOP, PS2, P1); | ||
29 | + } | ||
30 | +} |
1 | +package org.onlab.onos.net.intent; | ||
2 | + | ||
3 | +import static org.junit.Assert.assertEquals; | ||
4 | + | ||
5 | +import org.junit.Test; | ||
6 | +import org.onlab.onos.net.NetTestTools; | ||
7 | +import org.onlab.onos.net.Path; | ||
8 | + | ||
9 | +public class PathIntentTest extends ConnectivityIntentTest { | ||
10 | + // 111:11 --> 222:22 | ||
11 | + private static final Path PATH1 = NetTestTools.createPath("111", "222"); | ||
12 | + | ||
13 | + // 111:11 --> 333:33 | ||
14 | + private static final Path PATH2 = NetTestTools.createPath("222", "333"); | ||
15 | + | ||
16 | + @Test | ||
17 | + public void basics() { | ||
18 | + PathIntent intent = createOne(); | ||
19 | + assertEquals("incorrect id", IID, intent.getId()); | ||
20 | + assertEquals("incorrect match", MATCH, intent.getTrafficSelector()); | ||
21 | + assertEquals("incorrect action", NOP, intent.getTrafficTreatment()); | ||
22 | + assertEquals("incorrect ingress", P1, intent.getIngressPort()); | ||
23 | + assertEquals("incorrect egress", P2, intent.getEgressPort()); | ||
24 | + assertEquals("incorrect path", PATH1, intent.getPath()); | ||
25 | + } | ||
26 | + | ||
27 | + @Override | ||
28 | + protected PathIntent createOne() { | ||
29 | + return new PathIntent(IID, MATCH, NOP, P1, P2, PATH1); | ||
30 | + } | ||
31 | + | ||
32 | + @Override | ||
33 | + protected PathIntent createAnother() { | ||
34 | + return new PathIntent(IID, MATCH, NOP, P1, P3, PATH2); | ||
35 | + } | ||
36 | +} |
1 | +package org.onlab.onos.net.intent; | ||
2 | + | ||
3 | +import org.junit.Test; | ||
4 | + | ||
5 | +import static org.junit.Assert.assertEquals; | ||
6 | + | ||
7 | +/** | ||
8 | + * Suite of tests of the point-to-point intent descriptor. | ||
9 | + */ | ||
10 | +public class PointToPointIntentTest extends ConnectivityIntentTest { | ||
11 | + | ||
12 | + @Test | ||
13 | + public void basics() { | ||
14 | + PointToPointIntent intent = createOne(); | ||
15 | + assertEquals("incorrect id", IID, intent.getId()); | ||
16 | + assertEquals("incorrect match", MATCH, intent.getTrafficSelector()); | ||
17 | + assertEquals("incorrect ingress", P1, intent.getIngressPort()); | ||
18 | + assertEquals("incorrect egress", P2, intent.getEgressPort()); | ||
19 | + } | ||
20 | + | ||
21 | + @Override | ||
22 | + protected PointToPointIntent createOne() { | ||
23 | + return new PointToPointIntent(IID, MATCH, NOP, P1, P2); | ||
24 | + } | ||
25 | + | ||
26 | + @Override | ||
27 | + protected PointToPointIntent createAnother() { | ||
28 | + return new PointToPointIntent(IID, MATCH, NOP, P2, P1); | ||
29 | + } | ||
30 | +} |
1 | +package org.onlab.onos.net.intent; | ||
2 | + | ||
3 | +import org.junit.Test; | ||
4 | + | ||
5 | +import static org.junit.Assert.assertEquals; | ||
6 | + | ||
7 | +/** | ||
8 | + * Suite of tests of the single-to-multi point intent descriptor. | ||
9 | + */ | ||
10 | +public class SinglePointToMultiPointIntentTest extends ConnectivityIntentTest { | ||
11 | + | ||
12 | + @Test | ||
13 | + public void basics() { | ||
14 | + SinglePointToMultiPointIntent intent = createOne(); | ||
15 | + assertEquals("incorrect id", IID, intent.getId()); | ||
16 | + assertEquals("incorrect match", MATCH, intent.getTrafficSelector()); | ||
17 | + assertEquals("incorrect ingress", P1, intent.getIngressPort()); | ||
18 | + assertEquals("incorrect egress", PS2, intent.getEgressPorts()); | ||
19 | + } | ||
20 | + | ||
21 | + @Override | ||
22 | + protected SinglePointToMultiPointIntent createOne() { | ||
23 | + return new SinglePointToMultiPointIntent(IID, MATCH, NOP, P1, PS2); | ||
24 | + } | ||
25 | + | ||
26 | + @Override | ||
27 | + protected SinglePointToMultiPointIntent createAnother() { | ||
28 | + return new SinglePointToMultiPointIntent(IID, MATCH, NOP, P2, PS1); | ||
29 | + } | ||
30 | +} |
1 | +package org.onlab.onos.net.intent; | ||
2 | +//TODO is this the right package? | ||
3 | + | ||
4 | +/** | ||
5 | + * An installable intent used in the unit test. | ||
6 | + * | ||
7 | + * FIXME: we don't want to expose this class publicly, but the current Kryo | ||
8 | + * serialization mechanism does not allow this class to be private and placed | ||
9 | + * on testing directory. | ||
10 | + */ | ||
11 | +public class TestInstallableIntent extends AbstractIntent implements InstallableIntent { | ||
12 | + /** | ||
13 | + * Constructs an instance with the specified intent ID. | ||
14 | + * | ||
15 | + * @param id intent ID | ||
16 | + */ | ||
17 | + public TestInstallableIntent(IntentId id) { | ||
18 | + super(id); | ||
19 | + } | ||
20 | + | ||
21 | + /** | ||
22 | + * Constructor for serializer. | ||
23 | + */ | ||
24 | + protected TestInstallableIntent() { | ||
25 | + super(); | ||
26 | + } | ||
27 | + | ||
28 | +} |
1 | +package org.onlab.onos.net.intent; | ||
2 | +//TODO is this the right package? | ||
3 | + | ||
4 | +/** | ||
5 | + * An intent used in the unit test. | ||
6 | + * | ||
7 | + * FIXME: we don't want to expose this class publicly, but the current Kryo | ||
8 | + * serialization mechanism does not allow this class to be private and placed | ||
9 | + * on testing directory. | ||
10 | + */ | ||
11 | +public class TestIntent extends AbstractIntent { | ||
12 | + /** | ||
13 | + * Constructs an instance with the specified intent ID. | ||
14 | + * | ||
15 | + * @param id intent ID | ||
16 | + */ | ||
17 | + public TestIntent(IntentId id) { | ||
18 | + super(id); | ||
19 | + } | ||
20 | + | ||
21 | + /** | ||
22 | + * Constructor for serializer. | ||
23 | + */ | ||
24 | + protected TestIntent() { | ||
25 | + super(); | ||
26 | + } | ||
27 | +} |
1 | +package org.onlab.onos.net.intent; | ||
2 | +//TODO is this the right package? | ||
3 | + | ||
4 | +/** | ||
5 | + * An intent used in the unit test. | ||
6 | + * | ||
7 | + * FIXME: we don't want to expose this class publicly, but the current Kryo | ||
8 | + * serialization mechanism does not allow this class to be private and placed | ||
9 | + * on testing directory. | ||
10 | + */ | ||
11 | +public class TestSubclassInstallableIntent extends TestInstallableIntent implements InstallableIntent { | ||
12 | + /** | ||
13 | + * Constructs an instance with the specified intent ID. | ||
14 | + * | ||
15 | + * @param id intent ID | ||
16 | + */ | ||
17 | + public TestSubclassInstallableIntent(IntentId id) { | ||
18 | + super(id); | ||
19 | + } | ||
20 | + | ||
21 | + /** | ||
22 | + * Constructor for serializer. | ||
23 | + */ | ||
24 | + protected TestSubclassInstallableIntent() { | ||
25 | + super(); | ||
26 | + } | ||
27 | +} |
1 | +package org.onlab.onos.net.intent; | ||
2 | +//TODO is this the right package? | ||
3 | + | ||
4 | +/** | ||
5 | + * An intent used in the unit test. | ||
6 | + * | ||
7 | + * FIXME: we don't want to expose this class publicly, but the current Kryo | ||
8 | + * serialization mechanism does not allow this class to be private and placed | ||
9 | + * on testing directory. | ||
10 | + */ | ||
11 | +public class TestSubclassIntent extends TestIntent { | ||
12 | + /** | ||
13 | + * Constructs an instance with the specified intent ID. | ||
14 | + * | ||
15 | + * @param id intent ID | ||
16 | + */ | ||
17 | + public TestSubclassIntent(IntentId id) { | ||
18 | + super(id); | ||
19 | + } | ||
20 | + | ||
21 | + /** | ||
22 | + * Constructor for serializer. | ||
23 | + */ | ||
24 | + protected TestSubclassIntent() { | ||
25 | + super(); | ||
26 | + } | ||
27 | +} |
1 | +package org.onlab.onos.net.intent; | ||
2 | + | ||
3 | +import static org.junit.Assert.fail; | ||
4 | + | ||
5 | +/** | ||
6 | + * Set of test tools. | ||
7 | + */ | ||
8 | +public final class TestTools { | ||
9 | + | ||
10 | + // Disallow construction | ||
11 | + private TestTools() { | ||
12 | + } | ||
13 | + | ||
14 | + /** | ||
15 | + * Utility method to pause the current thread for the specified number of | ||
16 | + * milliseconds. | ||
17 | + * | ||
18 | + * @param ms number of milliseconds to pause | ||
19 | + */ | ||
20 | + public static void delay(int ms) { | ||
21 | + try { | ||
22 | + Thread.sleep(ms); | ||
23 | + } catch (InterruptedException e) { | ||
24 | + fail("unexpected interrupt"); | ||
25 | + } | ||
26 | + } | ||
27 | + | ||
28 | + /** | ||
29 | + * Periodically runs the given runnable, which should contain a series of | ||
30 | + * test assertions until all the assertions succeed, in which case it will | ||
31 | + * return, or until the the time expires, in which case it will throw the | ||
32 | + * first failed assertion error. | ||
33 | + * | ||
34 | + * @param start start time, in millis since start of epoch from which the | ||
35 | + * duration will be measured | ||
36 | + * @param delay initial delay (in milliseconds) before the first assertion | ||
37 | + * attempt | ||
38 | + * @param step delay (in milliseconds) between successive assertion | ||
39 | + * attempts | ||
40 | + * @param duration number of milliseconds beyond the given start time, | ||
41 | + * after which the failed assertions will be propagated and allowed | ||
42 | + * to fail the test | ||
43 | + * @param assertions runnable housing the test assertions | ||
44 | + */ | ||
45 | + public static void assertAfter(long start, int delay, int step, | ||
46 | + int duration, Runnable assertions) { | ||
47 | + delay(delay); | ||
48 | + while (true) { | ||
49 | + try { | ||
50 | + assertions.run(); | ||
51 | + break; | ||
52 | + } catch (AssertionError e) { | ||
53 | + if (System.currentTimeMillis() - start > duration) { | ||
54 | + throw e; | ||
55 | + } | ||
56 | + } | ||
57 | + delay(step); | ||
58 | + } | ||
59 | + } | ||
60 | + | ||
61 | + /** | ||
62 | + * Periodically runs the given runnable, which should contain a series of | ||
63 | + * test assertions until all the assertions succeed, in which case it will | ||
64 | + * return, or until the the time expires, in which case it will throw the | ||
65 | + * first failed assertion error. | ||
66 | + * <p> | ||
67 | + * The start of the period is the current time. | ||
68 | + * | ||
69 | + * @param delay initial delay (in milliseconds) before the first assertion | ||
70 | + * attempt | ||
71 | + * @param step delay (in milliseconds) between successive assertion | ||
72 | + * attempts | ||
73 | + * @param duration number of milliseconds beyond the current time time, | ||
74 | + * after which the failed assertions will be propagated and allowed | ||
75 | + * to fail the test | ||
76 | + * @param assertions runnable housing the test assertions | ||
77 | + */ | ||
78 | + public static void assertAfter(int delay, int step, int duration, | ||
79 | + Runnable assertions) { | ||
80 | + assertAfter(System.currentTimeMillis(), delay, step, duration, | ||
81 | + assertions); | ||
82 | + } | ||
83 | + | ||
84 | + /** | ||
85 | + * Periodically runs the given runnable, which should contain a series of | ||
86 | + * test assertions until all the assertions succeed, in which case it will | ||
87 | + * return, or until the the time expires, in which case it will throw the | ||
88 | + * first failed assertion error. | ||
89 | + * <p> | ||
90 | + * The start of the period is the current time and the first assertion | ||
91 | + * attempt is delayed by the value of {@code step} parameter. | ||
92 | + * | ||
93 | + * @param step delay (in milliseconds) between successive assertion | ||
94 | + * attempts | ||
95 | + * @param duration number of milliseconds beyond the current time time, | ||
96 | + * after which the failed assertions will be propagated and allowed | ||
97 | + * to fail the test | ||
98 | + * @param assertions runnable housing the test assertions | ||
99 | + */ | ||
100 | + public static void assertAfter(int step, int duration, | ||
101 | + Runnable assertions) { | ||
102 | + assertAfter(step, step, duration, assertions); | ||
103 | + } | ||
104 | + | ||
105 | + /** | ||
106 | + * Periodically runs the given runnable, which should contain a series of | ||
107 | + * test assertions until all the assertions succeed, in which case it will | ||
108 | + * return, or until the the time expires, in which case it will throw the | ||
109 | + * first failed assertion error. | ||
110 | + * <p> | ||
111 | + * The start of the period is the current time and each successive | ||
112 | + * assertion attempt is delayed by at least 10 milliseconds unless the | ||
113 | + * {@code duration} is less than that, in which case the one and only | ||
114 | + * assertion is made after that delay. | ||
115 | + * | ||
116 | + * @param duration number of milliseconds beyond the current time, | ||
117 | + * after which the failed assertions will be propagated and allowed | ||
118 | + * to fail the test | ||
119 | + * @param assertions runnable housing the test assertions | ||
120 | + */ | ||
121 | + public static void assertAfter(int duration, Runnable assertions) { | ||
122 | + int step = Math.min(duration, Math.max(10, duration / 10)); | ||
123 | + assertAfter(step, duration, assertions); | ||
124 | + } | ||
125 | + | ||
126 | +} |
1 | +package org.onlab.onos.net.intent; | ||
2 | + | ||
3 | +import java.util.List; | ||
4 | + | ||
5 | +/** | ||
6 | + * Abstraction of an extensible intent service enabled for unit tests. | ||
7 | + */ | ||
8 | +public interface TestableIntentService extends IntentService, IntentExtensionService { | ||
9 | + | ||
10 | + List<IntentException> getExceptions(); | ||
11 | + | ||
12 | +} |
... | @@ -48,6 +48,19 @@ | ... | @@ -48,6 +48,19 @@ |
48 | </dependency> | 48 | </dependency> |
49 | 49 | ||
50 | <dependency> | 50 | <dependency> |
51 | + <groupId>org.hamcrest</groupId> | ||
52 | + <artifactId>hamcrest-core</artifactId> | ||
53 | + <version>1.3</version> | ||
54 | + <scope>test</scope> | ||
55 | + </dependency> | ||
56 | + <dependency> | ||
57 | + <groupId>org.hamcrest</groupId> | ||
58 | + <artifactId>hamcrest-library</artifactId> | ||
59 | + <version>1.3</version> | ||
60 | + <scope>test</scope> | ||
61 | + </dependency> | ||
62 | + | ||
63 | + <dependency> | ||
51 | <groupId>org.slf4j</groupId> | 64 | <groupId>org.slf4j</groupId> |
52 | <artifactId>slf4j-api</artifactId> | 65 | <artifactId>slf4j-api</artifactId> |
53 | <version>1.7.6</version> | 66 | <version>1.7.6</version> |
... | @@ -244,6 +257,14 @@ | ... | @@ -244,6 +257,14 @@ |
244 | <artifactId>junit</artifactId> | 257 | <artifactId>junit</artifactId> |
245 | </dependency> | 258 | </dependency> |
246 | <dependency> | 259 | <dependency> |
260 | + <groupId>org.hamcrest</groupId> | ||
261 | + <artifactId>hamcrest-core</artifactId> | ||
262 | + </dependency> | ||
263 | + <dependency> | ||
264 | + <groupId>org.hamcrest</groupId> | ||
265 | + <artifactId>hamcrest-library</artifactId> | ||
266 | + </dependency> | ||
267 | + <dependency> | ||
247 | <groupId>org.slf4j</groupId> | 268 | <groupId>org.slf4j</groupId> |
248 | <artifactId>slf4j-jdk14</artifactId> | 269 | <artifactId>slf4j-jdk14</artifactId> |
249 | </dependency> | 270 | </dependency> |
... | @@ -320,6 +341,35 @@ | ... | @@ -320,6 +341,35 @@ |
320 | </plugin> | 341 | </plugin> |
321 | 342 | ||
322 | <!-- TODO: add findbugs plugin for static code analysis; for explicit invocation only --> | 343 | <!-- TODO: add findbugs plugin for static code analysis; for explicit invocation only --> |
344 | + <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.--> | ||
345 | + <plugin> | ||
346 | + <groupId>org.eclipse.m2e</groupId> | ||
347 | + <artifactId>lifecycle-mapping</artifactId> | ||
348 | + <version>1.0.0</version> | ||
349 | + <configuration> | ||
350 | + <lifecycleMappingMetadata> | ||
351 | + <pluginExecutions> | ||
352 | + <pluginExecution> | ||
353 | + <pluginExecutionFilter> | ||
354 | + <groupId>org.jacoco</groupId> | ||
355 | + <artifactId> | ||
356 | + jacoco-maven-plugin | ||
357 | + </artifactId> | ||
358 | + <versionRange> | ||
359 | + [0.7.1.201405082137,) | ||
360 | + </versionRange> | ||
361 | + <goals> | ||
362 | + <goal>prepare-agent</goal> | ||
363 | + </goals> | ||
364 | + </pluginExecutionFilter> | ||
365 | + <action> | ||
366 | + <ignore></ignore> | ||
367 | + </action> | ||
368 | + </pluginExecution> | ||
369 | + </pluginExecutions> | ||
370 | + </lifecycleMappingMetadata> | ||
371 | + </configuration> | ||
372 | + </plugin> | ||
323 | </plugins> | 373 | </plugins> |
324 | </pluginManagement> | 374 | </pluginManagement> |
325 | 375 | ... | ... |
-
Please register or login to post a comment