public void mergeRuntimeUpdate(
      List<TimelineEvent> pendingTimeline, Map<String, Artifact> pendingArtifacts) {
    if (timeline.addAll(pendingTimeline)) {
      synced = false;
    }
    if (pendingArtifacts != null && !pendingArtifacts.isEmpty()) {
      for (Map.Entry<String, Artifact> entry : pendingArtifacts.entrySet()) {
        String key = entry.getKey();
        if (!entry.getValue().equals(artifacts.get(key))) {
          if (artifacts.containsKey(key)
              && artifacts.get(key).getType() == Artifact.Type.DEFAULT
              && entry.getValue().getType() == Artifact.Type.DEFAULT) {
            artifacts.get(key).asDefault().getData().putAll(entry.getValue().asDefault().getData());
          } else {
            artifacts.put(entry.getKey(), entry.getValue());
          }
          synced = false;
        }
      }
    }
    if (!synced) {
      runtimeState.setModifyTime(System.currentTimeMillis());
    }
  }
@Test
  public void testMergeTimeline() throws Exception {
    StepRuntimeSummary summary =
        loadObject(
            "fixtures/execution/sample-step-runtime-summary-1.json", StepRuntimeSummary.class);
    assertTrue(summary.isSynced());
    TimelineEvent curEvent = summary.getTimeline().getTimelineEvents().get(0);
    TimelineEvent newEvent = TimelineLogEvent.builder().message("world").build();
    summary.mergeRuntimeUpdate(Collections.singletonList(newEvent), null);
    assertFalse(summary.isSynced());
    assertEquals(new Timeline(Arrays.asList(curEvent, newEvent)), summary.getTimeline());
  }