/*
* Copyright 2012 LinkedIn, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.linkedin.parseq.trace.codec.json;
import com.linkedin.parseq.TaskType;
import com.linkedin.parseq.internal.IdGenerator;
import com.linkedin.parseq.trace.Relationship;
import com.linkedin.parseq.trace.ResultType;
import com.linkedin.parseq.trace.ShallowTraceBuilder;
import com.linkedin.parseq.trace.Trace;
import com.linkedin.parseq.trace.TraceBuilder;
import com.linkedin.parseq.trace.TraceRelationship;
import com.linkedin.parseq.trace.codec.TraceCodec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.testng.annotations.Test;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.fail;
/**
* @author Chi Chan (ckchan@linkedin.com)
* @author Chris Pettitt
* @author Jaroslaw Odzga (jodzga@linkedin.com)
*/
public class TestJsonTraceCodec {
private TraceCodec _codec = new JsonTraceCodec();
@Test
public void testReversibleUnstartedTrace() throws IOException {
final ShallowTraceBuilder test =
new ShallowTraceBuilder(IdGenerator.getNextId()).setName("test").setResultType(ResultType.UNFINISHED)
.setTaskType(TaskType.FUSION.getName());
final Trace trace = Trace.single(test.build(), "test", 0L);
assertReversible(trace);
}
@Test
public void testReversibleSuccessfulTrace() throws IOException {
final ShallowTraceBuilder test =
new ShallowTraceBuilder(IdGenerator.getNextId()).setName("test").setResultType(ResultType.SUCCESS)
.setValue("test value").setStartNanos(0L).setPendingNanos(50L).setEndNanos(100L);
final Trace trace = Trace.single(test.build(), "test", 0L);
assertReversible(trace);
}
@Test
public void testReversibleSuccessfulTraceWithNullValue() throws IOException {
final ShallowTraceBuilder test = new ShallowTraceBuilder(IdGenerator.getNextId()).setName("test")
.setResultType(ResultType.SUCCESS).setStartNanos(0L).setPendingNanos(50L).setEndNanos(100L);
final Trace trace = Trace.single(test.build(), "test", 0L);
assertReversible(trace);
}
@Test
public void testReversibleWithSingleAttributes() throws IOException {
final ShallowTraceBuilder test =
new ShallowTraceBuilder(IdGenerator.getNextId()).setName("test").setResultType(ResultType.SUCCESS)
.setStartNanos(0L).setPendingNanos(50L).addAttribute("key1", "value1").setEndNanos(100L);
final Trace trace = Trace.single(test.build(), "test", 0L);
assertReversible(trace);
}
@Test
public void testReversibleWithMultipleAttributes() throws IOException {
final ShallowTraceBuilder test = new ShallowTraceBuilder(IdGenerator.getNextId()).setName("test")
.setResultType(ResultType.SUCCESS).setStartNanos(0L).setPendingNanos(50L).addAttribute("key1", "value1")
.addAttribute("key2", "value2").addAttribute("key3", "value3").setEndNanos(100L);
final Trace trace = Trace.single(test.build(), "test", 0L);
assertReversible(trace);
}
@Test
public void testReversibleWithRemoveAttributes() throws IOException {
final ShallowTraceBuilder test = new ShallowTraceBuilder(IdGenerator.getNextId()).setName("test")
.setResultType(ResultType.SUCCESS).setStartNanos(0L).setPendingNanos(50L).addAttribute("key1", "value1")
.addAttribute("key2", "value2").addAttribute("key3", "value3").removeAttribute("key1").removeAttribute("key2")
.removeAttribute("key3").setEndNanos(100L);
final Trace trace = Trace.single(test.build(), "test", 0L);
assertReversible(trace);
}
@Test
public void testWithDupAttributes() throws IOException {
final ShallowTraceBuilder test = new ShallowTraceBuilder(IdGenerator.getNextId()).setName("test")
.setResultType(ResultType.SUCCESS).setStartNanos(0L).setPendingNanos(50L).addAttribute("key1", "value1")
.addAttribute("key2", "value2").addAttribute("key2", "value3").setEndNanos(100L);
final Trace trace = Trace.single(test.build(), "test", 0L);
assertReversible(trace);
}
@Test
public void testWithNonExistingRemoveAttributes() throws IOException {
final ShallowTraceBuilder test = new ShallowTraceBuilder(IdGenerator.getNextId()).setName("test")
.setResultType(ResultType.SUCCESS).setStartNanos(0L).setPendingNanos(50L).removeAttribute("key1")
.removeAttribute("key2").removeAttribute("key3").setEndNanos(100L);
final Trace trace = Trace.single(test.build(), "test", 0L);
assertReversible(trace);
}
@Test
public void testReversibleErrorTrace() throws IOException {
// If we have started a task we also must set the end time
final ShallowTraceBuilder test =
new ShallowTraceBuilder(IdGenerator.getNextId()).setName("test").setResultType(ResultType.ERROR)
.setValue("error value").setStartNanos(0L).setPendingNanos(50L).setEndNanos(100L);
final Trace trace = Trace.single(test.build(), "test", 0L);
assertReversible(trace);
}
@Test
public void testReversibleUnfinishedTrace() throws IOException {
// If we have started a task we also must set the end time
final ShallowTraceBuilder test = new ShallowTraceBuilder(IdGenerator.getNextId()).setName("test")
.setResultType(ResultType.UNFINISHED).setStartNanos(0L).setPendingNanos(50L).setEndNanos(100L);
final Trace trace = Trace.single(test.build(), "test", 0L);
assertReversible(trace);
}
@Test
public void testReversibleWithHiddenTrace() throws IOException {
final ShallowTraceBuilder test =
new ShallowTraceBuilder(IdGenerator.getNextId()).setName("test").setResultType(ResultType.SUCCESS)
.setValue("test value").setStartNanos(0L).setPendingNanos(50L).setHidden(true).setEndNanos(100L);
final Trace trace = Trace.single(test.build(), "test", 0L);
assertReversible(trace);
}
@Test
public void testReversibleTraceWithChild() throws IOException {
final ShallowTraceBuilder parent =
new ShallowTraceBuilder(IdGenerator.getNextId()).setName("parent").setResultType(ResultType.SUCCESS)
.setValue("parent value").setStartNanos(0L).setPendingNanos(100L).setEndNanos(200L);
final ShallowTraceBuilder child =
new ShallowTraceBuilder(IdGenerator.getNextId()).setName("child").setResultType(ResultType.SUCCESS)
.setValue("child value").setStartNanos(50L).setPendingNanos(75L).setEndNanos(100L);
final TraceBuilder builder = new TraceBuilder(1024, "test", 0L);
builder.addRelationship(Relationship.PARENT_OF, parent, child);
Trace trace = builder.build();
assertReversible(trace);
}
@Test
public void testReversibleTraceWithPredecessor() throws IOException {
final ShallowTraceBuilder predecessor =
new ShallowTraceBuilder(IdGenerator.getNextId()).setName("predecessor").setResultType(ResultType.SUCCESS)
.setValue("predecessor value").setStartNanos(0L).setPendingNanos(100L).setEndNanos(200L);
final ShallowTraceBuilder successor =
new ShallowTraceBuilder(IdGenerator.getNextId()).setName("successor").setResultType(ResultType.SUCCESS)
.setValue("successor value").setStartNanos(50L).setPendingNanos(75L).setEndNanos(100L);
final TraceBuilder builder = new TraceBuilder(1024, "test", 0L);
builder.addRelationship(Relationship.SUCCESSOR_OF, successor, predecessor);
Trace trace = builder.build();
assertReversible(trace);
}
@Test
public void testReversibleTraceWithDiamond() throws IOException {
final ShallowTraceBuilder source =
new ShallowTraceBuilder(IdGenerator.getNextId()).setName("source").setResultType(ResultType.SUCCESS)
.setValue("source value").setStartNanos(0L).setPendingNanos(25L).setEndNanos(50L);
final ShallowTraceBuilder left =
new ShallowTraceBuilder(IdGenerator.getNextId()).setName("left").setResultType(ResultType.SUCCESS)
.setValue("left value").setStartNanos(50L).setPendingNanos(75L).setEndNanos(100L);
final ShallowTraceBuilder right =
new ShallowTraceBuilder(IdGenerator.getNextId()).setName("right").setResultType(ResultType.SUCCESS)
.setValue("right value").setStartNanos(50L).setPendingNanos(75L).setEndNanos(100L);
final ShallowTraceBuilder sink =
new ShallowTraceBuilder(IdGenerator.getNextId()).setName("sink").setResultType(ResultType.SUCCESS)
.setValue("sink value").setStartNanos(100L).setPendingNanos(125L).setEndNanos(150L);
final TraceBuilder builder = new TraceBuilder(1024, "test", 0L);
builder.addRelationship(Relationship.SUCCESSOR_OF, left, source);
builder.addRelationship(Relationship.SUCCESSOR_OF, right, source);
builder.addRelationship(Relationship.SUCCESSOR_OF, sink, left);
builder.addRelationship(Relationship.SUCCESSOR_OF, sink, right);
Trace trace = builder.build();
assertReversible(trace);
}
// We use this to ensure that the building we do below is working correctly
@Test
public void testCustomBuilding() throws IOException {
final String json = buildJson(
new String[] { traceStr(1, "parent", ResultType.UNFINISHED, false), traceStr(2, "child", ResultType.UNFINISHED,
false), traceStr(3, "predecessor", ResultType.UNFINISHED, false) },
new String[] { hierStr(1, 2), orderStr(3, 1) }, "test", 0L);
final Trace trace;
try {
trace = decodeString(json);
} catch (IOException e) {
fail("JSON parse failed. Document:\n" + json + "\nError: " + e.toString());
return;
}
assertEquals("parent", trace.getTraceMap().get(1L).getName());
assertEquals(ResultType.UNFINISHED, trace.getTraceMap().get(1L).getResultType());
assertEquals(2, trace.getRelationships().size());
String childName = null;
String predecessorName = null;
for (TraceRelationship rel : trace.getRelationships()) {
if (rel.getRelationhsip() == Relationship.PARENT_OF) {
childName = trace.getTraceMap().get(rel.getTo()).getName();
} else if (rel.getRelationhsip() == Relationship.SUCCESSOR_OF) {
predecessorName = trace.getTraceMap().get(rel.getTo()).getName();
}
}
assertEquals("child", childName);
assertEquals("predecessor", predecessorName);
}
@Test
public void testEmptyDocument() throws IOException {
try {
decodeString("{}");
fail("Expected IOException");
} catch (IOException e) {
// expected case
}
}
@Test
public void testDocWithNoTraces() throws IOException {
final String json = buildJson(new String[0], new String[0], "test", 0L);
Trace trace = decodeString(json);
assertEquals(0, trace.getTraceMap().size());
assertEquals(0, trace.getRelationships().size());
}
@Test
public void testDocWithTraceHavingTaskType() throws IOException {
final String json =
buildJson(new String[] {traceStr(1, "name", ResultType.UNFINISHED, false, TaskType.FUSION.getName())}, new String[0], "test", 0L);
Trace decodedTrace = decodeString(json);
assertEquals(decodedTrace.getTraceMap().get(1L).getTaskType(), TaskType.FUSION.getName());
}
@Test
public void testDocWitInvalidPredecessorReference() throws IOException {
final String json =
buildJson(new String[] { traceStr(1, "name", ResultType.UNFINISHED, false) }, new String[] { orderStr(2, 1) }, "test", 0L);
try {
decodeString(json);
fail("Expected IOException");
} catch (IOException e) {
// expected case
}
}
@Test
public void testDocWitInvalidSuccessorReference() {
final String json =
buildJson(new String[] { traceStr(1, "name", ResultType.UNFINISHED, false) }, new String[] { orderStr(1, 2) }, "test", 0L);
try {
decodeString(json);
fail("Expected IOException");
} catch (IOException e) {
// expected case
}
}
@Test
public void testDocWitInvalidChildReference() throws IOException {
final String json =
buildJson(new String[] { traceStr(1, "name", ResultType.UNFINISHED, false) }, new String[] { hierStr(1, 2) }, "test", 0L);
try {
decodeString(json);
fail("Expected IOException");
} catch (IOException e) {
// expected case
}
}
@Test
public void testDocWitInvalidParentReference() throws IOException {
final String json =
buildJson(new String[] { traceStr(1, "name", ResultType.UNFINISHED, false) }, new String[] { hierStr(2, 1) }, "test", 0L);
try {
decodeString(json);
fail("Expected IOException");
} catch (IOException e) {
// expected case
}
}
private String buildJson(final String[] traces, final String[] related, String planClass, Long planId) {
final StringBuilder sb = new StringBuilder();
sb.append("{")
.append(quote(JsonTraceCodec.PLAN_CLASS)).append(": ").append(quote(planClass)).append(",")
.append(quote(JsonTraceCodec.PLAN_ID)).append(": ").append(planId).append(",")
.append(arrayFieldStr(JsonTraceCodec.TRACES, traces)).append(",")
.append(arrayFieldStr(JsonTraceCodec.RELATIONSHIPS, related))
.append("}");
return sb.toString();
}
private String traceStr(final int id, final String name, final ResultType resultType, final boolean hidden) {
return traceStr(id, name, resultType, hidden, null);
}
private String traceStr(final int id, final String name, final ResultType resultType, final boolean hidden,
final String taskType) {
final StringBuilder sb = new StringBuilder();
sb.append("{").append(quote(JsonTraceCodec.TRACE_ID)).append(": ").append(id).append(",")
.append(quote(JsonTraceCodec.TRACE_NAME)).append(": ").append(quote(name)).append(",")
.append(quote(JsonTraceCodec.TRACE_HIDDEN)).append(": ").append(hidden).append(",")
.append(quote(JsonTraceCodec.TRACE_RESULT_TYPE)).append(": ").append(quote(resultType.toString()));
if (taskType != null) {
sb.append(",").append(quote(JsonTraceCodec.TRACE_TASK_TYPE)).append(": ").append(quote(taskType));
}
sb.append("}");
return sb.toString();
}
private String hierStr(final int parentId, final int childId) {
final StringBuilder sb = new StringBuilder();
sb.append("{").append(quote(JsonTraceCodec.RELATIONSHIP_RELATIONSHIP)).append(": ")
.append(quote(Relationship.PARENT_OF.name())).append(",").append(quote(JsonTraceCodec.RELATIONSHIP_FROM))
.append(": ").append(parentId).append(",").append(quote(JsonTraceCodec.RELATIONSHIP_TO)).append(": ")
.append(childId).append("}");
return sb.toString();
}
private String orderStr(final int predecessorId, final int successorId) {
final StringBuilder sb = new StringBuilder();
sb.append("{").append(quote(JsonTraceCodec.RELATIONSHIP_RELATIONSHIP)).append(": ")
.append(quote(Relationship.SUCCESSOR_OF.name())).append(",").append(quote(JsonTraceCodec.RELATIONSHIP_FROM))
.append(": ").append(successorId).append(",").append(quote(JsonTraceCodec.RELATIONSHIP_TO)).append(": ")
.append(predecessorId).append("}");
return sb.toString();
}
private String arrayFieldStr(final String name, final String... elems) {
final StringBuilder sb = new StringBuilder();
sb.append("\"").append(name).append("\": [");
for (int i = 0; i < elems.length; i++) {
sb.append(" ").append(elems[i]);
if (i + 1 < elems.length) {
sb.append(",");
}
}
sb.append(" ]");
return sb.toString();
}
private String quote(String name) {
return "\"" + name + "\"";
}
private Trace decodeString(final String str) throws IOException {
return _codec.decode(new ByteArrayInputStream(str.getBytes()));
}
private void assertReversible(final Trace trace) throws IOException {
assertReversibleStream(trace);
assertReversibleString(trace);
}
private void assertReversibleStream(final Trace trace) throws IOException {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
_codec.encode(trace, baos);
final byte[] json = baos.toByteArray();
final Trace deserialized;
try {
deserialized = _codec.decode(new ByteArrayInputStream(json));
} catch (IOException e) {
fail("Exception during deserialization: " + e.getMessage() + "\n" + new String(json));
// Make the compiler happy by claiming to return here
return;
}
assertEquals(trace, deserialized);
}
private void assertReversibleString(final Trace trace) throws IOException {
final String json = _codec.encode(trace);
final Trace deserialized;
try {
deserialized = _codec.decode(json);
} catch (IOException e) {
fail("Exception during deserialization: " + e.getMessage() + "\n" + json);
// Make the compiler happy by claiming to return here
return;
}
assertEquals(trace, deserialized);
}
}