Unit tests for Instruction and Ethernet codecs

Added tests for codecs for Ethernet and Instruction classes
Also fixed some bugs uncovered by the tests

Change-Id: I29f82d169e81b3fca417f88fab992148bf36dc71
diff --git a/core/api/src/test/java/org/onosproject/net/flow/instructions/InstructionsTest.java b/core/api/src/test/java/org/onosproject/net/flow/instructions/InstructionsTest.java
index 8470654..d8b511c 100644
--- a/core/api/src/test/java/org/onosproject/net/flow/instructions/InstructionsTest.java
+++ b/core/api/src/test/java/org/onosproject/net/flow/instructions/InstructionsTest.java
@@ -94,6 +94,8 @@
         assertThatClassIsImmutable(L2ModificationInstruction.ModVlanIdInstruction.class);
         assertThatClassIsImmutable(L2ModificationInstruction.ModVlanPcpInstruction.class);
         assertThatClassIsImmutable(L3ModificationInstruction.ModIPInstruction.class);
+        assertThatClassIsImmutable(L2ModificationInstruction.ModMplsLabelInstruction.class);
+        assertThatClassIsImmutable(L2ModificationInstruction.PushHeaderInstructions.class);
     }
 
     //  DropInstruction
diff --git a/web/api/src/main/java/org/onosproject/codec/impl/EthernetCodec.java b/web/api/src/main/java/org/onosproject/codec/impl/EthernetCodec.java
index 1bacec2..c426a46 100644
--- a/web/api/src/main/java/org/onosproject/codec/impl/EthernetCodec.java
+++ b/web/api/src/main/java/org/onosproject/codec/impl/EthernetCodec.java
@@ -36,13 +36,23 @@
     public ObjectNode encode(Ethernet ethernet, CodecContext context) {
         checkNotNull(ethernet, "Ethernet cannot be null");
 
-        return context.mapper().createObjectNode()
-                .put("destinationMacAddress", ethernet.getDestinationMAC().toString())
-                .put("sourceMacAddress", ethernet.getSourceMAC().toString())
-                .put("priorityCode", ethernet.getPriorityCode())
+        final ObjectNode result = context.mapper().createObjectNode()
                 .put("vlanId", ethernet.getVlanID())
                 .put("etherType", ethernet.getEtherType())
+                .put("priorityCode", ethernet.getPriorityCode())
                 .put("pad", ethernet.isPad());
+
+        if (ethernet.getDestinationMAC() != null) {
+            result.put("destMac",
+                       ethernet.getDestinationMAC().toString());
+        }
+
+        if (ethernet.getSourceMAC() != null) {
+            result.put("srcMac",
+                       ethernet.getSourceMAC().toString());
+        }
+
+        return result;
     }
 
 }
diff --git a/web/api/src/main/java/org/onosproject/codec/impl/InstructionCodec.java b/web/api/src/main/java/org/onosproject/codec/impl/InstructionCodec.java
index 7207e13..81bd432 100644
--- a/web/api/src/main/java/org/onosproject/codec/impl/InstructionCodec.java
+++ b/web/api/src/main/java/org/onosproject/codec/impl/InstructionCodec.java
@@ -147,7 +147,7 @@
             case OUTPUT:
                 final Instructions.OutputInstruction outputInstruction =
                         (Instructions.OutputInstruction) instruction;
-                result.put("portNumber", outputInstruction.port().toLong());
+                result.put("port", outputInstruction.port().toLong());
                 break;
 
             case DROP:
diff --git a/web/api/src/test/java/org/onosproject/codec/impl/EthernetCodecTest.java b/web/api/src/test/java/org/onosproject/codec/impl/EthernetCodecTest.java
new file mode 100644
index 0000000..847b0d0
--- /dev/null
+++ b/web/api/src/test/java/org/onosproject/codec/impl/EthernetCodecTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.codec.impl;
+
+import org.junit.Test;
+import org.onlab.packet.Ethernet;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onosproject.codec.impl.EthernetJsonMatcher.matchesEthernet;
+
+/**
+ * Unit test for Ethernet class codec.
+ */
+public class EthernetCodecTest {
+
+    /**
+     * Unit test for the ethernet object codec.
+     */
+    @Test
+    public void ethernetCodecTest() {
+        final CodecContext context = new MockCodecContext();
+        final JsonCodec<Ethernet> ethernetCodec = context.codec(Ethernet.class);
+        assertThat(ethernetCodec, notNullValue());
+
+        final Ethernet eth1 = new Ethernet();
+        eth1.setSourceMACAddress("11:22:33:44:55:01");
+        eth1.setDestinationMACAddress("11:22:33:44:55:02");
+        eth1.setPad(true);
+        eth1.setEtherType(Ethernet.TYPE_ARP);
+        eth1.setPriorityCode((byte) 7);
+        eth1.setVlanID((short) 33);
+
+        final ObjectNode eth1Json = ethernetCodec.encode(eth1, context);
+        assertThat(eth1Json, notNullValue());
+        assertThat(eth1Json, matchesEthernet(eth1));
+    }
+}
diff --git a/web/api/src/test/java/org/onosproject/codec/impl/EthernetJsonMatcher.java b/web/api/src/test/java/org/onosproject/codec/impl/EthernetJsonMatcher.java
new file mode 100644
index 0000000..fd4190e
--- /dev/null
+++ b/web/api/src/test/java/org/onosproject/codec/impl/EthernetJsonMatcher.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.codec.impl;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.onlab.packet.Ethernet;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Hamcrest matcher for ethernet objects.
+ */
+public class EthernetJsonMatcher extends TypeSafeMatcher<JsonNode> {
+
+    private final Ethernet ethernet;
+    private String reason = "";
+
+    public EthernetJsonMatcher(Ethernet ethernetValue) {
+        ethernet = ethernetValue;
+    }
+
+    @Override
+    public boolean matchesSafely(JsonNode jsonEthernet) {
+
+        // check source MAC
+        final JsonNode jsonSourceMacNode = jsonEthernet.get("srcMac");
+        if (ethernet.getSourceMAC() != null) {
+            final String jsonSourceMac = jsonSourceMacNode.textValue();
+            final String sourceMac = ethernet.getSourceMAC().toString();
+            if (!jsonSourceMac.equals(sourceMac)) {
+                reason = "source MAC " + ethernet.getSourceMAC().toString();
+                return false;
+            }
+        } else {
+            //  source MAC not specified, JSON representation must be empty
+            if (jsonSourceMacNode != null) {
+                reason = "source mac should be null ";
+                return false;
+            }
+        }
+
+        // check destination MAC
+        final JsonNode jsonDestinationMacNode = jsonEthernet.get("destMac");
+        if (ethernet.getDestinationMAC() != null) {
+            final String jsonDestinationMac = jsonDestinationMacNode.textValue();
+            final String destinationMac = ethernet.getDestinationMAC().toString();
+            if (!jsonDestinationMac.equals(destinationMac)) {
+                reason = "destination MAC " + ethernet.getDestinationMAC().toString();
+                return false;
+            }
+        } else {
+            //  destination MAC not specified, JSON representation must be empty
+            if (jsonDestinationMacNode != null) {
+                reason = "destination mac should be null ";
+                return false;
+            }
+        }
+
+        // check priority code
+        final short jsonPriorityCode = jsonEthernet.get("priorityCode").shortValue();
+        final short priorityCode = ethernet.getPriorityCode();
+        if (jsonPriorityCode != priorityCode) {
+            reason = "priority code " + Short.toString(ethernet.getPriorityCode());
+            return false;
+        }
+
+        // check vlanId
+        final short jsonVlanId = jsonEthernet.get("vlanId").shortValue();
+        final short vlanId = ethernet.getVlanID();
+        if (jsonVlanId != vlanId) {
+            reason = "vlan id " + Short.toString(ethernet.getVlanID());
+            return false;
+        }
+
+        // check etherType
+        final short jsonEtherType = jsonEthernet.get("etherType").shortValue();
+        final short etherType = ethernet.getEtherType();
+        if (jsonEtherType != etherType) {
+            reason = "etherType " + Short.toString(ethernet.getEtherType());
+            return false;
+        }
+
+        // check pad
+        final boolean jsonPad = jsonEthernet.get("pad").asBoolean();
+        final boolean pad = ethernet.isPad();
+        if (jsonPad != pad) {
+            reason = "pad " + Boolean.toString(ethernet.isPad());
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendText(reason);
+    }
+
+    /**
+     * Factory to allocate a ethernet matcher.
+     *
+     * @param ethernet ethernet object we are looking for
+     * @return matcher
+     */
+    public static EthernetJsonMatcher matchesEthernet(Ethernet ethernet) {
+        return new EthernetJsonMatcher(ethernet);
+    }
+}
diff --git a/web/api/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java b/web/api/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java
new file mode 100644
index 0000000..781f133
--- /dev/null
+++ b/web/api/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.codec.impl;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.instructions.L0ModificationInstruction;
+import org.onosproject.net.flow.instructions.L2ModificationInstruction;
+import org.onosproject.net.flow.instructions.L3ModificationInstruction;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onosproject.codec.impl.InstructionJsonMatcher.matchesInstruction;
+
+/**
+ * Unit tests for Instruction codec.
+ */
+public class InstructionCodecTest {
+    CodecContext context;
+    JsonCodec<Instruction> instructionCodec;
+
+    /**
+     * Sets up for each tests.  Creates a context and fetches the instruction
+     * codec.
+     */
+    @Before
+    public void setUp() {
+        context = new MockCodecContext();
+        instructionCodec = context.codec(Instruction.class);
+        assertThat(instructionCodec, notNullValue());
+    }
+
+    /**
+     * Tests the encoding of push header instructions.
+     */
+    @Test
+    public void pushHeaderInstructionsTest() {
+        final L2ModificationInstruction.PushHeaderInstructions instruction =
+                (L2ModificationInstruction.PushHeaderInstructions) Instructions.pushMpls();
+        final ObjectNode instructionJson = instructionCodec.encode(instruction, context);
+
+        assertThat(instructionJson, matchesInstruction(instruction));
+    }
+
+    /**
+     * Tests the encoding of drop instructions.
+     */
+    @Test
+    public void dropInstructionTest() {
+        final Instructions.DropInstruction instruction =
+                new Instructions.DropInstruction();
+        final ObjectNode instructionJson =
+                instructionCodec.encode(instruction, context);
+        assertThat(instructionJson, matchesInstruction(instruction));
+    }
+
+    /**
+     * Tests the encoding of output instructions.
+     */
+    @Test
+    public void outputInstructionTest() {
+        final Instructions.OutputInstruction instruction =
+                Instructions.createOutput(PortNumber.portNumber(22));
+        final ObjectNode instructionJson =
+                instructionCodec.encode(instruction, context);
+        assertThat(instructionJson, matchesInstruction(instruction));
+    }
+
+    /**
+     * Tests the encoding of mod lambda instructions.
+     */
+    @Test
+    public void modLambdaInstructionTest() {
+        final L0ModificationInstruction.ModLambdaInstruction instruction =
+                (L0ModificationInstruction.ModLambdaInstruction)
+                        Instructions.modL0Lambda((short) 55);
+        final ObjectNode instructionJson =
+                instructionCodec.encode(instruction, context);
+        assertThat(instructionJson, matchesInstruction(instruction));
+    }
+
+    /**
+     * Tests the encoding of mod ether instructions.
+     */
+    @Test
+    public void modEtherInstructionTest() {
+        final L2ModificationInstruction.ModEtherInstruction instruction =
+                (L2ModificationInstruction.ModEtherInstruction)
+                        Instructions.modL2Src(MacAddress.valueOf("11:22:33:44:55:66"));
+        final ObjectNode instructionJson =
+                instructionCodec.encode(instruction, context);
+        assertThat(instructionJson, matchesInstruction(instruction));
+    }
+
+    /**
+     * Tests the encoding of mod vlan id instructions.
+     */
+    @Test
+    public void modVlanIdInstructionTest() {
+        final L2ModificationInstruction.ModVlanIdInstruction instruction =
+                (L2ModificationInstruction.ModVlanIdInstruction)
+                        Instructions.modVlanId(VlanId.vlanId((short) 12));
+
+        final ObjectNode instructionJson =
+                instructionCodec.encode(instruction, context);
+        assertThat(instructionJson, matchesInstruction(instruction));
+    }
+
+    /**
+     * Tests the encoding of mod vlan pcp instructions.
+     */
+    @Test
+    public void modVlanPcpInstructionTest() {
+        final L2ModificationInstruction.ModVlanPcpInstruction instruction =
+                (L2ModificationInstruction.ModVlanPcpInstruction)
+                        Instructions.modVlanPcp((byte) 9);
+        final ObjectNode instructionJson =
+                instructionCodec.encode(instruction, context);
+        assertThat(instructionJson, matchesInstruction(instruction));
+    }
+
+    /**
+     * Tests the encoding of mod ip instructions.
+     */
+    @Test
+    public void modIPInstructionTest() {
+        final IpAddress ip = IpPrefix.valueOf("1.2.3.4/24").address();
+        final L3ModificationInstruction.ModIPInstruction instruction =
+                (L3ModificationInstruction.ModIPInstruction)
+                        Instructions.modL3Src(ip);
+        final ObjectNode instructionJson =
+                instructionCodec.encode(instruction, context);
+        assertThat(instructionJson, matchesInstruction(instruction));
+    }
+
+    /**
+     * Tests the encoding of mod MPLS label instructions.
+     */
+    @Test
+    public void modMplsLabelInstructionTest() {
+        final L2ModificationInstruction.ModMplsLabelInstruction instruction =
+                (L2ModificationInstruction.ModMplsLabelInstruction)
+                        Instructions.modMplsLabel(99);
+        final ObjectNode instructionJson =
+                instructionCodec.encode(instruction, context);
+        assertThat(instructionJson, matchesInstruction(instruction));
+    }
+
+}
diff --git a/web/api/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java b/web/api/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java
new file mode 100644
index 0000000..d5b4ae0
--- /dev/null
+++ b/web/api/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.codec.impl;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.net.flow.instructions.Instruction;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import static org.onosproject.codec.impl.EthernetJsonMatcher.matchesEthernet;
+import static org.onosproject.net.flow.instructions.Instructions.*;
+import static org.onosproject.net.flow.instructions.L0ModificationInstruction.*;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.*;
+import static org.onosproject.net.flow.instructions.L3ModificationInstruction.*;
+
+/**
+ * Hamcrest matcher for instructions.
+ */
+public class InstructionJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+    private final Instruction instruction;
+
+    public InstructionJsonMatcher(Instruction instructionValue) {
+        instruction = instructionValue;
+    }
+
+    /**
+     * Matches the contents of a push header instruction.
+     *
+     * @param instructionJson JSON instruction to match
+     * @return true if contents match, false otherwise
+     */
+    private boolean matchPushHeaderInstruction(JsonNode instructionJson,
+                                               Description description) {
+        PushHeaderInstructions instructionToMatch =
+                (PushHeaderInstructions) instruction;
+        final String jsonSubtype = instructionJson.get("subtype").textValue();
+        if (!instructionToMatch.subtype().name().equals(jsonSubtype)) {
+            description.appendText("subtype was " + jsonSubtype);
+            return false;
+        }
+
+        final String jsonType = instructionJson.get("type").textValue();
+        if (!instructionToMatch.type().name().equals(jsonType)) {
+            description.appendText("type was " + jsonType);
+            return false;
+        }
+
+        final JsonNode ethJson = instructionJson.get("ethernetType");
+        if (ethJson == null) {
+            description.appendText("ethernetType was not null");
+            return false;
+        }
+
+        final Matcher ethernetMatcher =
+                matchesEthernet(instructionToMatch.ethernetType());
+        final boolean ethernetMatches = ethernetMatcher.matches(ethJson);
+        if (!ethernetMatches) {
+            ethernetMatcher.describeMismatch(ethernetMatcher, description);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Matches the contents of an output instruction.
+     *
+     * @param instructionJson JSON instruction to match
+     * @return true if contents match, false otherwise
+     */
+    private boolean matchOutputInstruction(JsonNode instructionJson,
+                                           Description description) {
+        OutputInstruction instructionToMatch = (OutputInstruction) instruction;
+
+        final String jsonType = instructionJson.get("type").textValue();
+        if (!instructionToMatch.type().name().equals(jsonType)) {
+            description.appendText("type was " + jsonType);
+            return false;
+        }
+
+        final long jsonPort = instructionJson.get("port").asLong();
+        if (instructionToMatch.port().toLong() != jsonPort) {
+            description.appendText("port was " + jsonPort);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Matches the contents of a mod lambda instruction.
+     *
+     * @param instructionJson JSON instruction to match
+     * @return true if contents match, false otherwise
+     */
+    private boolean matchModLambdaInstruction(JsonNode instructionJson,
+                                              Description description) {
+        ModLambdaInstruction instructionToMatch =
+                (ModLambdaInstruction) instruction;
+        final String jsonSubtype = instructionJson.get("subtype").textValue();
+        if (!instructionToMatch.subtype().name().equals(jsonSubtype)) {
+            description.appendText("subtype was " + jsonSubtype);
+            return false;
+        }
+
+        final String jsonType = instructionJson.get("type").textValue();
+        if (!instructionToMatch.type().name().equals(jsonType)) {
+            description.appendText("type was " + jsonType);
+            return false;
+        }
+
+        final long jsonLambda = instructionJson.get("lambda").shortValue();
+        if (instructionToMatch.lambda() != jsonLambda) {
+            description.appendText("lambda was " + jsonLambda);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Matches the contents of a mod lambda instruction.
+     *
+     * @param instructionJson JSON instruction to match
+     * @return true if contents match, false otherwise
+     */
+    private boolean matchModEtherInstruction(JsonNode instructionJson,
+                                             Description description) {
+        ModEtherInstruction instructionToMatch =
+                (ModEtherInstruction) instruction;
+        final String jsonSubtype = instructionJson.get("subtype").textValue();
+        if (!instructionToMatch.subtype().name().equals(jsonSubtype)) {
+            description.appendText("subtype was " + jsonSubtype);
+            return false;
+        }
+
+        final String jsonType = instructionJson.get("type").textValue();
+        if (!instructionToMatch.type().name().equals(jsonType)) {
+            description.appendText("type was " + jsonType);
+            return false;
+        }
+
+        final String jsonMac = instructionJson.get("mac").textValue();
+        final String mac = instructionToMatch.mac().toString();
+        if (!mac.equals(jsonMac)) {
+            description.appendText("mac was " + jsonMac);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Matches the contents of a mod vlan id instruction.
+     *
+     * @param instructionJson JSON instruction to match
+     * @return true if contents match, false otherwise
+     */
+    private boolean matchModVlanIdInstruction(JsonNode instructionJson,
+                                           Description description) {
+        ModVlanIdInstruction instructionToMatch =
+                (ModVlanIdInstruction) instruction;
+        final String jsonSubtype = instructionJson.get("subtype").textValue();
+        if (!instructionToMatch.subtype().name().equals(jsonSubtype)) {
+            description.appendText("subtype was " + jsonSubtype);
+            return false;
+        }
+
+        final String jsonType = instructionJson.get("type").textValue();
+        if (!instructionToMatch.type().name().equals(jsonType)) {
+            description.appendText("type was " + jsonType);
+            return false;
+        }
+
+        final short jsonVlanId = instructionJson.get("vlanId").shortValue();
+        final short vlanId = instructionToMatch.vlanId().toShort();
+        if (jsonVlanId != vlanId) {
+            description.appendText("vlan id was " + jsonVlanId);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Matches the contents of a mod vlan pcp instruction.
+     *
+     * @param instructionJson JSON instruction to match
+     * @return true if contents match, false otherwise
+     */
+    private boolean matchModVlanPcpInstruction(JsonNode instructionJson,
+                                              Description description) {
+        ModVlanPcpInstruction instructionToMatch =
+                (ModVlanPcpInstruction) instruction;
+        final String jsonSubtype = instructionJson.get("subtype").textValue();
+        if (!instructionToMatch.subtype().name().equals(jsonSubtype)) {
+            description.appendText("subtype was " + jsonSubtype);
+            return false;
+        }
+
+        final String jsonType = instructionJson.get("type").textValue();
+        if (!instructionToMatch.type().name().equals(jsonType)) {
+            description.appendText("type was " + jsonType);
+            return false;
+        }
+
+        final short jsonVlanPcp = instructionJson.get("vlanPcp").shortValue();
+        final short vlanId = instructionToMatch.vlanPcp();
+        if (jsonVlanPcp != vlanId) {
+            description.appendText("vlan pcp was " + jsonVlanPcp);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Matches the contents of a mod ip instruction.
+     *
+     * @param instructionJson JSON instruction to match
+     * @return true if contents match, false otherwise
+     */
+    private boolean matchModIpInstruction(JsonNode instructionJson,
+                                          Description description) {
+        ModIPInstruction instructionToMatch =
+                (ModIPInstruction) instruction;
+        final String jsonSubtype = instructionJson.get("subtype").textValue();
+        if (!instructionToMatch.subtype().name().equals(jsonSubtype)) {
+            description.appendText("subtype was " + jsonSubtype);
+            return false;
+        }
+
+        final String jsonType = instructionJson.get("type").textValue();
+        if (!instructionToMatch.type().name().equals(jsonType)) {
+            description.appendText("type was " + jsonType);
+            return false;
+        }
+
+        final String jsonIp = instructionJson.get("ip").textValue();
+        final String ip = instructionToMatch.ip().toString();
+        if (!ip.equals(jsonIp)) {
+            description.appendText("ip was " + jsonIp);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Matches the contents of a mod ip instruction.
+     *
+     * @param instructionJson JSON instruction to match
+     * @return true if contents match, false otherwise
+     */
+    private boolean matchModMplsLabelInstruction(JsonNode instructionJson,
+                                          Description description) {
+        ModMplsLabelInstruction instructionToMatch =
+                (ModMplsLabelInstruction) instruction;
+        final String jsonSubtype = instructionJson.get("subtype").textValue();
+        if (!instructionToMatch.subtype().name().equals(jsonSubtype)) {
+            description.appendText("subtype was " + jsonSubtype);
+            return false;
+        }
+
+        final String jsonType = instructionJson.get("type").textValue();
+        if (!instructionToMatch.type().name().equals(jsonType)) {
+            description.appendText("type was " + jsonType);
+            return false;
+        }
+
+        final int jsonLabel = instructionJson.get("label").intValue();
+        final int label = instructionToMatch.label();
+        if (label != jsonLabel) {
+            description.appendText("ip was " + jsonLabel);
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean matchesSafely(JsonNode jsonInstruction, Description description) {
+
+        // check type
+        final JsonNode jsonTypeNode = jsonInstruction.get("type");
+        final String jsonType = jsonTypeNode.textValue();
+        final String type = instruction.type().name();
+        if (!jsonType.equals(type)) {
+                description.appendText("type was " + type);
+                return false;
+        }
+
+        if (instruction instanceof PushHeaderInstructions) {
+            return matchPushHeaderInstruction(jsonInstruction, description);
+        } else if (instruction instanceof DropInstruction) {
+            return true;
+        } else if (instruction instanceof OutputInstruction) {
+            return matchOutputInstruction(jsonInstruction, description);
+        } else if (instruction instanceof ModLambdaInstruction) {
+            return matchModLambdaInstruction(jsonInstruction, description);
+        } else if (instruction instanceof ModEtherInstruction) {
+            return matchModEtherInstruction(jsonInstruction, description);
+        } else if (instruction instanceof ModVlanIdInstruction) {
+            return matchModVlanIdInstruction(jsonInstruction, description);
+        } else if (instruction instanceof ModVlanPcpInstruction) {
+            return matchModVlanPcpInstruction(jsonInstruction, description);
+        } else if (instruction instanceof ModIPInstruction) {
+            return matchModIpInstruction(jsonInstruction, description);
+        } else if (instruction instanceof ModMplsLabelInstruction) {
+            return matchModMplsLabelInstruction(jsonInstruction, description);
+        }
+
+        return false;
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendText(instruction.toString());
+    }
+
+    /**
+     * Factory to allocate an instruction matcher.
+     *
+     * @param instruction instruction object we are looking for
+     * @return matcher
+     */
+    public static InstructionJsonMatcher matchesInstruction(Instruction instruction) {
+        return new InstructionJsonMatcher(instruction);
+    }
+}
diff --git a/web/api/src/test/java/org/onosproject/codec/impl/MockCodecContext.java b/web/api/src/test/java/org/onosproject/codec/impl/MockCodecContext.java
new file mode 100644
index 0000000..f54f523
--- /dev/null
+++ b/web/api/src/test/java/org/onosproject/codec/impl/MockCodecContext.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.codec.impl;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Mock codec context for use in codec unit tests.
+ */
+public final class MockCodecContext implements CodecContext {
+
+    private ObjectMapper mapper = new ObjectMapper();
+    private CodecManager manager = new CodecManager();
+
+    public MockCodecContext() {
+        manager.activate();
+    }
+
+    @Override
+    public ObjectMapper mapper() {
+        return mapper;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> JsonCodec<T> codec(Class<T> entityClass) {
+        return manager.getCodec(entityClass);
+    }
+
+    @Override
+    public <T> T get(Class<T> serviceClass) {
+        return null;
+    }
+
+}