/** * Copyright (C) 2010-14 diirt developers. See COPYRIGHT.TXT * All rights reserved. Use is subject to license terms. See LICENSE.TXT */ package org.diirt.datasource.integration; import java.io.PrintStream; import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import org.diirt.datasource.PVReaderEvent; import org.diirt.datasource.PVReaderListener; import org.diirt.datasource.PVWriterEvent; import org.diirt.datasource.PVWriterListener; import org.diirt.util.time.TimeDuration; import org.diirt.vtype.Alarm; import org.diirt.vtype.VNumber; import org.diirt.vtype.ValueUtil; /** * * @author carcassi */ public class Log { private final List<Event> events = Collections.synchronizedList(new ArrayList<Event>()); private final List<String> errors = Collections.synchronizedList(new ArrayList<String>()); private final AtomicInteger testCount = new AtomicInteger(0); public <T> PVReaderListener<T> createReadListener() { return new PVReaderListener<T>() { @Override public void pvChanged(PVReaderEvent<T> event) { events.add(new ReadEvent(Instant.now(), event.getPvReader().getName(), event, event.getPvReader().isConnected(), event.getPvReader().getValue(), event.getPvReader().lastException())); } }; } public <T> PVWriterListener<T> createWriteListener(final String name) { return new PVWriterListener<T>() { @Override public void pvChanged(PVWriterEvent<T> event) { events.add(new WriteEvent(Instant.now(), name, event, event.getPvWriter().isWriteConnected(), event.getPvWriter().lastWriteException())); } }; } public List<Event> getEvents() { return events; } public boolean isCorrect() { return errors.isEmpty(); } public List<String> getErrors() { return errors; } public int getTestCount() { return testCount.get(); } public void matchConnections(String pvName, boolean... connectionFlags) { int current = 0; for (Event event : events) { if (event instanceof ReadEvent && event.getPvName().equals(pvName)) { ReadEvent readEvent = (ReadEvent) event; if (readEvent.getEvent().isConnectionChanged()) { if (current < connectionFlags.length && readEvent.isConnected() != connectionFlags[current]) { errors.add(pvName + ": connection notification " + current + " was " + readEvent.isConnected() + " (expected " + connectionFlags[current] + ")"); } current++; } } } if (current > connectionFlags.length) { errors.add(pvName + ": more connection notifications (" + current + ") than expected (" + connectionFlags.length + ")"); } if (current < connectionFlags.length) { errors.add(pvName + ": fewer connection notifications (" + current + ") than expected (" + connectionFlags.length + ")"); } testCount.incrementAndGet(); } public void matchWriteConnections(String pvName, boolean... connectionFlags) { int current = 0; for (Event event : events) { if (event instanceof WriteEvent && event.getPvName().equals(pvName)) { WriteEvent writeEvent = (WriteEvent) event; if (writeEvent.getEvent().isConnectionChanged()) { if (current < connectionFlags.length && writeEvent.isConnected() != connectionFlags[current]) { errors.add(pvName + ": write connection notification " + current + " was " + writeEvent.isConnected() + " (expected " + connectionFlags[current] + ")"); } current++; } } } if (current > connectionFlags.length) { errors.add(pvName + ": more write connection notifications (" + current + ") than expected (" + connectionFlags.length + ")"); } if (current < connectionFlags.length) { errors.add(pvName + ": fewer write connection notifications (" + current + ") than expected (" + connectionFlags.length + ")"); } testCount.incrementAndGet(); } public void matchWriteNotifications(String pvName, boolean... sucessfulWrite) { int current = 0; for (Event event : events) { if (event instanceof WriteEvent && event.getPvName().equals(pvName)) { WriteEvent writeEvent = (WriteEvent) event; if (writeEvent.getEvent().isWriteSucceeded()) { if (current < sucessfulWrite.length && !sucessfulWrite[current]) { errors.add(pvName + ": write notification " + current + " was successful (expected unsuccessful)"); } current++; } else if (writeEvent.getEvent().isWriteFailed()) { if (current < sucessfulWrite.length && sucessfulWrite[current]) { errors.add(pvName + ": write notification " + current + " was unsuccessful (expected successful)"); } current++; } } } if (current > sucessfulWrite.length) { errors.add(pvName + ": more write notifications (" + current + ") than expected (" + sucessfulWrite.length + ")"); } if (current < sucessfulWrite.length) { errors.add(pvName + ": fewer write notifications (" + current + ") than expected (" + sucessfulWrite.length + ")"); } testCount.incrementAndGet(); } public void matchValues(String pvName, VTypeMatchMask mask, Object... values) { int current = 0; for (Event event : events) { if (event instanceof ReadEvent && event.getPvName().equals(pvName)) { ReadEvent readEvent = (ReadEvent) event; if (readEvent.getEvent().isValueChanged()) { Object actualValue = readEvent.getValue(); if (current < values.length) { Object expectedValue = values[current]; String message = mask.match(expectedValue, actualValue); if (message != null) { errors. add(pvName + ": value notification " + current + " " + message); } } current++; } } } if (current > values.length) { errors.add(pvName + ": more value notification (" + current + ") than expected (" + values.length + ")"); } if (current < values.length) { errors.add(pvName + ": fewer value notification (" + current + ") notification than expected (" + values.length + ")"); } testCount.incrementAndGet(); } public void validate(String pvName, Validator validator) { List<String> resErrors = validator.validate(valuesForChannel(pvName, Object.class)); for (String error : resErrors) { errors.add(pvName + ": " + error); } testCount.incrementAndGet(); } public void matchAllValues(String pvName, VTypeMatchMask mask, Object expectedValue) { int current = 0; for (Event event : events) { if (event instanceof ReadEvent && event.getPvName().equals(pvName)) { ReadEvent readEvent = (ReadEvent) event; if (readEvent.getEvent().isValueChanged()) { Object actualValue = readEvent.getValue(); String message = mask.match(expectedValue, actualValue); if (message != null) { errors. add(pvName + ": value notification " + current + " " + message); } current++; } } } testCount.incrementAndGet(); } public void matchErrors(String pvName, String... messages) { int current = 0; for (Event event : events) { if (event instanceof ReadEvent && event.getPvName().equals(pvName)) { ReadEvent readEvent = (ReadEvent) event; if (readEvent.getEvent().isExceptionChanged()) { if (current < messages.length && Objects.equals(readEvent.getLastException().getMessage(), messages[current])) { errors.add(pvName + ": error notification " + current + " was " + readEvent.getLastException().getMessage() + " (expected " + messages[current] + ")"); } current++; } } } if (current > messages.length) { errors.add(pvName + ": more error notifications (" + current + ") than expected (" + messages.length + ")"); } if (current < messages.length) { errors.add(pvName + ": fewer error notifications (" + current + ") than expected (" + messages.length + ")"); } testCount.incrementAndGet(); } public void matchSequentialNumberValues(String pvName, int expectedRepeatedValues) { List<VNumber> values = valuesForChannel(pvName, VNumber.class); Double currentValue = null; int repeatedValues = 0; for (VNumber vNumber : values) { if (vNumber == null) { errors.add(pvName + ": value was null"); } else { if (currentValue == null) { currentValue = vNumber.getValue().doubleValue(); } else { double nextValue = vNumber.getValue().doubleValue(); if (nextValue == currentValue) { repeatedValues++; } else if (nextValue != currentValue + 1) { errors.add(pvName + ": value was not sequential (" + nextValue + " after " + currentValue + ")"); } currentValue = nextValue; } } } if (repeatedValues != expectedRepeatedValues) { errors.add(pvName + ": repeated value occurences mismatch (" + repeatedValues + " but expected " + expectedRepeatedValues + ")"); } testCount.incrementAndGet(); } void matchValueEventRate(String pvName, double minRateHz, double maxRateHz) { Instant initialTime = null; Instant finalTime = null; int nNotifications = 0; for (Event event : events) { if (pvName.equals(event.getPvName()) && event instanceof ReadEvent) { ReadEvent readEvent = (ReadEvent) event; if (readEvent.getEvent().isValueChanged()) { Instant nextTime = readEvent.getTimestamp(); if (initialTime == null) { initialTime = nextTime; } else { finalTime = nextTime; } nNotifications++; } } } if (initialTime != null && finalTime != null) { double seconds = TimeDuration.toSecondsDouble(Duration.between(initialTime, finalTime)); // The period between the first two notification is going to be shorter // since we connect independently from the cycle. // We'll make sure the rate is between the two extreems: second event // is right after, second event is after the correct period double minMeasuredRate = (nNotifications - 2) / seconds; double maxMeasuredRate = (nNotifications - 1) / seconds; if (maxMeasuredRate < minRateHz || minMeasuredRate > maxRateHz) { errors.add(pvName + ": event rate mismatch (" + minMeasuredRate + "/" + maxMeasuredRate + " but expected between " + minRateHz + "/" + maxRateHz + ")"); } } testCount.incrementAndGet(); } private <T> List<T> valuesForChannel(String pvName, Class<T> clazz) { List<T> values = new ArrayList<>(); for (Event event : events) { if (pvName.equals(event.getPvName()) && event instanceof ReadEvent) { ReadEvent readEvent = (ReadEvent) event; if (readEvent.getEvent().isValueChanged()) { try { T value = clazz.cast(readEvent.getValue()); values.add(value); } catch(ClassCastException ex) { errors.add(pvName + ": value is not " + clazz.getSimpleName() + " (was " + readEvent.getValue() + ")"); } } } } return values; } private List<Alarm> alarmsForChannel(String pvName) { List<Alarm> values = new ArrayList<>(); for (Event event : events) { if (pvName.equals(event.getPvName()) && event instanceof ReadEvent) { ReadEvent readEvent = (ReadEvent) event; if (readEvent.getEvent().isValueChanged()) { values.add(ValueUtil.alarmOf(readEvent.getValue())); } } } return values; } private DateTimeFormatter format = DateTimeFormatter.ofPattern("ss.NNNNNNNNN"); public void print(PrintStream out) { for (Event event : events) { if (event instanceof ReadEvent) { ReadEvent readEvent = (ReadEvent) event; out.append(format.format(ZonedDateTime.ofInstant(readEvent.getTimestamp(), ZoneId.systemDefault()))) .append(" R("); if (readEvent.getEvent().isConnectionChanged()) { out.append("C"); } if (readEvent.getEvent().isValueChanged()) { out.append("V"); } if (readEvent.getEvent().isExceptionChanged()) { out.append("E"); } out.append(") ").append(readEvent.getPvName()); if (readEvent.isConnected()) { out.append(" CONN "); } else { out.append(" DISC "); } out.append(Objects.toString(readEvent.getValue())); if (readEvent.getLastException() != null) { out.append(" ").append(readEvent.getLastException().getClass().getName()) .append(":").append(readEvent.getLastException().getMessage()); } else { out.append(" NoException"); } } if (event instanceof WriteEvent) { WriteEvent writeEvent = (WriteEvent) event; out.append(format.format(ZonedDateTime.ofInstant(writeEvent.getTimestamp(), ZoneId.systemDefault()))) .append(" W("); if (writeEvent.getEvent().isConnectionChanged()) { out.append("C"); } if (writeEvent.getEvent().isWriteSucceeded()) { out.append("S"); } if (writeEvent.getEvent().isWriteFailed()) { out.append("F"); } if (writeEvent.getEvent().isExceptionChanged()) { out.append("E"); } out.append(") ").append(writeEvent.getPvName()); if (writeEvent.isConnected()) { out.append(" CONN"); } else { out.append(" DISC"); } if (writeEvent.getLastException() != null) { out.append(" ").append(writeEvent.getLastException().getClass().getName()) .append(":").append(writeEvent.getLastException().getMessage()); } else { out.append(" NoException"); } } out.append("\n"); } out.flush(); } }