diff --git a/analysis/src/test/java/ComputeTest.java b/analysis/src/test/java/ComputeTest.java index 5433fc2..81d55c2 100644 --- a/analysis/src/test/java/ComputeTest.java +++ b/analysis/src/test/java/ComputeTest.java @@ -13,9 +13,9 @@ public class ComputeTest { private final static SensorMetadata metadata = SensorMetadata - .withNewComponent("cp1", null, true, null, false) - .withNewComponent("cp2", null, true, null, false) - .withNewComponent("cp3", null, true, null, false) + .withNewComponent("cp1", null, true, "mW", false) + .withNewComponent("cp2", null, true, "mW", false) + .withNewComponent("cp3", null, true, "mW", false) .build(); @Test diff --git a/measure/src/main/java/net/laprun/sustainability/power/analysis/TotalMeasureProcessor.java b/measure/src/main/java/net/laprun/sustainability/power/analysis/TotalMeasureProcessor.java index 6a767e2..db2271b 100644 --- a/measure/src/main/java/net/laprun/sustainability/power/analysis/TotalMeasureProcessor.java +++ b/measure/src/main/java/net/laprun/sustainability/power/analysis/TotalMeasureProcessor.java @@ -15,9 +15,11 @@ public class TotalMeasureProcessor implements MeasureProcessor { private double maxTotal; private double accumulatedTotal; private final Function formula; + private final SensorUnit expectedResultUnit; - public TotalMeasureProcessor(SensorMetadata metadata, int... totalComponentIndices) { + public TotalMeasureProcessor(SensorMetadata metadata, SensorUnit expectedResultUnit, int... totalComponentIndices) { Objects.requireNonNull(totalComponentIndices, "Must specify component indices that will aggregated in a total"); + this.expectedResultUnit = Objects.requireNonNull(expectedResultUnit, "Must specify expected result unit"); final var errors = new Errors(); final var totalComponents = Arrays.stream(totalComponentIndices) @@ -42,25 +44,30 @@ public TotalMeasureProcessor(SensorMetadata metadata, int... totalComponentIndic private TotalComponent toTotalComponent(SensorMetadata metadata, int index, Errors errors) { final var cm = metadata.metadataFor(index); final var name = cm.name(); - if (!cm.isWattCommensurable()) { + final var unit = cm.unit(); + if (!unit.isCommensurableWith(expectedResultUnit)) { errors.addError("Component " + name - + " is not commensurate with a power measure. It needs to be expressible in Watts."); + + " is not commensurable with the expected base unit: " + expectedResultUnit); } - final var factor = SensorUnit.of(cm.unit()).getUnit().factor(); + final var factor = unit.factor(); return new TotalComponent(name, index, factor); } public double total() { - return accumulatedTotal; + return convertToExpectedUnit(accumulatedTotal); } public double minMeasuredTotal() { - return minTotal; + return convertToExpectedUnit(minTotal); } public double maxMeasuredTotal() { - return maxTotal; + return convertToExpectedUnit(maxTotal); + } + + private double convertToExpectedUnit(double value) { + return value * expectedResultUnit.base().conversionFactorTo(expectedResultUnit); } private record TotalComponent(String name, int index, double factor) { diff --git a/measure/src/test/java/net/laprun/sustainability/power/analysis/TotalMeasureProcessorTest.java b/measure/src/test/java/net/laprun/sustainability/power/analysis/TotalMeasureProcessorTest.java new file mode 100644 index 0000000..03ca479 --- /dev/null +++ b/measure/src/test/java/net/laprun/sustainability/power/analysis/TotalMeasureProcessorTest.java @@ -0,0 +1,73 @@ +package net.laprun.sustainability.power.analysis; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; + +import net.laprun.sustainability.power.SensorMetadata; +import net.laprun.sustainability.power.SensorUnit; + +class TotalMeasureProcessorTest { + + @Test + void totalShouldFailIfAllComponentsAreNotCommensurable() { + final var inError = "cp2"; + final var metadata = SensorMetadata + .withNewComponent("cp1", null, true, "mW", true) + .withNewComponent(inError, null, true, "mJ", true) + .build(); + + final var expectedResultUnit = SensorUnit.W; + final var e = assertThrows(IllegalArgumentException.class, + () -> new TotalMeasureProcessor(metadata, expectedResultUnit, 0, 1)); + assertTrue(e.getMessage().contains("Component " + inError + + " is not commensurable with the expected base unit: " + expectedResultUnit)); + } + + @Test + void testTotal() { + final var metadata = SensorMetadata + .withNewComponent("cp1", null, true, "mW", true) + .withNewComponent("cp2", null, true, "mW", true) + .withNewComponent("cp3", null, true, "mW", true) + .build(); + + final var m1c1 = 10.0; + final var m1c2 = 12.0; + final var m1c3 = 0.0; + final var m1total = m1c1 + m1c2 + m1c3; + final var m2c1 = 8.0; + final var m2c2 = 17.0; + final var m2c3 = 0.0; + final var m2total = m2c1 + m2c2 + m2c3; + final var m3c1 = 5.0; + final var m3c2 = 5.0; + final var m3c3 = 0.0; + final var m3total = m3c1 + m3c2 + m3c3; + + final var totalProc = new TotalMeasureProcessor(metadata, SensorUnit.of("mW"), 0, 1, 2); + + final var components = new double[metadata.componentCardinality()]; + components[0] = m1c1; + components[1] = m1c2; + components[2] = m1c3; + totalProc.recordMeasure(components, System.currentTimeMillis()); + + components[0] = m2c1; + components[1] = m2c2; + components[2] = m2c3; + totalProc.recordMeasure(components, System.currentTimeMillis()); + + components[0] = m3c1; + components[1] = m3c2; + components[2] = m3c3; + totalProc.recordMeasure(components, System.currentTimeMillis()); + + assertEquals(m1c1 + m1c2 + m1c3 + m2c1 + m2c2 + m2c3 + m3c1 + m3c2 + m3c3, totalProc.total()); + assertEquals(Stream.of(m1total, m2total, m3total).min(Double::compareTo).orElseThrow(), totalProc.minMeasuredTotal()); + assertEquals(Stream.of(m1total, m2total, m3total).max(Double::compareTo).orElseThrow(), totalProc.maxMeasuredTotal()); + } + +} diff --git a/measure/src/test/java/net/laprun/sustainability/power/measure/OngoingPowerMeasureTest.java b/measure/src/test/java/net/laprun/sustainability/power/measure/OngoingPowerMeasureTest.java index 57fc368..80685a7 100644 --- a/measure/src/test/java/net/laprun/sustainability/power/measure/OngoingPowerMeasureTest.java +++ b/measure/src/test/java/net/laprun/sustainability/power/measure/OngoingPowerMeasureTest.java @@ -1,19 +1,16 @@ package net.laprun.sustainability.power.measure; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.random.RandomGenerator; -import java.util.stream.Stream; import org.junit.jupiter.api.Test; import net.laprun.sustainability.power.SensorMetadata; import net.laprun.sustainability.power.analysis.ComponentProcessor; -import net.laprun.sustainability.power.analysis.TotalMeasureProcessor; public class OngoingPowerMeasureTest { private final static SensorMetadata metadata = SensorMetadata @@ -25,9 +22,9 @@ public class OngoingPowerMeasureTest { @Test void checkThatTotalComponentIsProperlyAdded() { final var metadata = SensorMetadata - .withNewComponent("cp1", null, true, null, false) - .withNewComponent("cp2", null, true, null, false) - .withNewComponent("cp3", null, true, null, false) + .withNewComponent("cp1", null, true, "mW", false) + .withNewComponent("cp2", null, true, "mW", false) + .withNewComponent("cp3", null, true, "mW", false) .build(); var measure = new OngoingPowerMeasure(metadata); assertThat(measure.metadata().totalComponents()).isEmpty(); @@ -75,46 +72,6 @@ void testBasics() { assertThat(measures.measures()).isEqualTo(new double[] { m3c1, m3c2, m3c3 }); } - @Test - void testTotal() { - final var m1c1 = 10.0; - final var m1c2 = 12.0; - final var m1c3 = 0.0; - final var m1total = m1c1 + m1c2 + m1c3; - final var m2c1 = 8.0; - final var m2c2 = 17.0; - final var m2c3 = 0.0; - final var m2total = m2c1 + m2c2 + m2c3; - final var m3c1 = 5.0; - final var m3c2 = 5.0; - final var m3c3 = 0.0; - final var m3total = m3c1 + m3c2 + m3c3; - - final var measure = new OngoingPowerMeasure(metadata); - final var totalProc = new TotalMeasureProcessor(metadata, 0, 1, 2); - measure.registerMeasureProcessor(totalProc); - - final var components = new double[metadata.componentCardinality()]; - components[0] = m1c1; - components[1] = m1c2; - components[2] = m1c3; - measure.recordMeasure(components); - - components[0] = m2c1; - components[1] = m2c2; - components[2] = m2c3; - measure.recordMeasure(components); - - components[0] = m3c1; - components[1] = m3c2; - components[2] = m3c3; - measure.recordMeasure(components); - - assertEquals(m1c1 + m1c2 + m1c3 + m2c1 + m2c2 + m2c3 + m3c1 + m3c2 + m3c3, totalProc.total()); - assertEquals(Stream.of(m1total, m2total, m3total).min(Double::compareTo).orElseThrow(), totalProc.minMeasuredTotal()); - assertEquals(Stream.of(m1total, m2total, m3total).max(Double::compareTo).orElseThrow(), totalProc.maxMeasuredTotal()); - } - @Test void processorsShouldBeCalled() { final var random = Random.from(RandomGenerator.getDefault()); diff --git a/metadata/pom.xml b/metadata/pom.xml index afb4bdb..58c4b72 100644 --- a/metadata/pom.xml +++ b/metadata/pom.xml @@ -13,10 +13,6 @@ Metadata model used by the power-server project, extracted to allow reuse in client projects - - eu.hoefel - units - org.assertj assertj-core diff --git a/metadata/src/main/java/net/laprun/sustainability/power/SensorMetadata.java b/metadata/src/main/java/net/laprun/sustainability/power/SensorMetadata.java index e0f114e..7687b9f 100644 --- a/metadata/src/main/java/net/laprun/sustainability/power/SensorMetadata.java +++ b/metadata/src/main/java/net/laprun/sustainability/power/SensorMetadata.java @@ -11,7 +11,6 @@ import java.util.Objects; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; /** @@ -23,6 +22,7 @@ public class SensorMetadata { private final Map components; @JsonProperty("documentation") private final String documentation; + // todo: remove @JsonProperty("totalComponents") private final int[] totalComponents; @@ -42,6 +42,7 @@ public SensorMetadata(List components, String documentation) final var errors = new Errors(); final var indices = new BitSet(cardinality); final var totalIndices = new BitSet(cardinality); + final var baseUnit = new SensorUnit[1]; components.forEach(component -> { // check that index is valid final var index = component.index; @@ -58,18 +59,6 @@ public SensorMetadata(List components, String documentation) indices.set(index); } - // check that component's unit is commensurable to Watts if included in total - if (component.isIncludedInTotal) { - if (indexValid) { - totalIndices.set(index); - } - - if (!component.isWattCommensurable()) { - errors.addError("Component " + component.name - + " is not commensurate with a power measure. It needs to be expressible in Watts."); - } - } - if (this.components.containsKey(component.name)) { errors.addError("Multiple components are named '" + component.name + "': " + components.stream().filter(cm -> cm.name.equals(component.name)).toList()); @@ -100,7 +89,14 @@ public SensorMetadata(List components, String documentation) this.totalComponents = totalComponents; } - public static SensorMetadata.Builder withNewComponent(String name, String description, boolean isAttributed, String unit, + public static SensorMetadata.Builder withNewComponent(String name, String description, boolean isAttributed, + String unitSymbol, + boolean participatesInTotal) { + return new SensorMetadata.Builder().withNewComponent(name, description, isAttributed, unitSymbol, participatesInTotal); + } + + public static SensorMetadata.Builder withNewComponent(String name, String description, boolean isAttributed, + SensorUnit unit, boolean participatesInTotal) { return new SensorMetadata.Builder().withNewComponent(name, description, isAttributed, unit, participatesInTotal); } @@ -207,7 +203,14 @@ public static class Builder { private int currentIndex = 0; private String documentation; - public Builder withNewComponent(String name, String description, boolean isAttributed, String unit, + public Builder withNewComponent(String name, String description, boolean isAttributed, String unitSymbol, + boolean isIncludedInTotal) { + components + .add(new ComponentMetadata(name, currentIndex++, description, isAttributed, unitSymbol, isIncludedInTotal)); + return this; + } + + public Builder withNewComponent(String name, String description, boolean isAttributed, SensorUnit unit, boolean isIncludedInTotal) { components.add(new ComponentMetadata(name, currentIndex++, description, isAttributed, unit, isIncludedInTotal)); return this; @@ -240,23 +243,21 @@ public SensorMetadata build() { * metric for that sensor. Components that take part of the total computation must use a unit commensurable with * {@link SensorUnit#W} */ - public record ComponentMetadata(String name, int index, String description, boolean isAttributed, String unit, + public record ComponentMetadata(String name, int index, String description, boolean isAttributed, SensorUnit unit, boolean isIncludedInTotal) { public ComponentMetadata { if (name == null) { throw new IllegalArgumentException("Component name cannot be null"); } + if (unit == null) { + throw new IllegalArgumentException("Component unit cannot be null"); + } } - /** - * Determines whether or not this component is measuring power (i.e. its value can be converted to Watts) - * - * @return {@code true} if this component's unit is commensurable to Watts, {@code false} otherwise - */ - @JsonIgnore - public boolean isWattCommensurable() { - return unit != null && SensorUnit.of(unit).isWattCommensurable(); + public ComponentMetadata(String name, int index, String description, boolean isAttributed, String unitSymbol, + boolean isIncludedInTotal) { + this(name, index, description, isAttributed, SensorUnit.of(unitSymbol), isIncludedInTotal); } } } diff --git a/metadata/src/main/java/net/laprun/sustainability/power/SensorUnit.java b/metadata/src/main/java/net/laprun/sustainability/power/SensorUnit.java index b11186e..c98c9ce 100644 --- a/metadata/src/main/java/net/laprun/sustainability/power/SensorUnit.java +++ b/metadata/src/main/java/net/laprun/sustainability/power/SensorUnit.java @@ -1,37 +1,105 @@ package net.laprun.sustainability.power; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; -import eu.hoefel.unit.Unit; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; public class SensorUnit { - private final Unit unit; + private final static Map prefixesToFactors = Map.of( + "n", 1e-9, + "µ", 1e-6, + "m", 1e-3, + "c", 1e-2, + "d", 10.0, + "h", 100.0, + "k", 1e3, + "M", 1e6, + "G", 1e9); + private final static Map baseUnits = new HashMap<>(); + private final static Map knownUnits = new HashMap<>(); + private final String symbol; + private final SensorUnit base; + private final double factor; + + public SensorUnit(String symbol, SensorUnit base, double factor) { + this.symbol = Objects.requireNonNull(symbol).trim(); + this.base = base != null ? base : this; + this.factor = factor < 0 ? 1 : factor; + } + + @JsonCreator + public static SensorUnit of(String symbol) { + var unit = baseUnits.get(symbol); + if (unit != null) { + return unit; + } + + unit = knownUnits.get(symbol); + if (unit != null) { + return unit; + } - private SensorUnit(String symbol) { - this.symbol = symbol; - this.unit = Unit.of(symbol); + symbol = Objects.requireNonNull(symbol, "Unit symbol cannot be null").trim(); + if (symbol.isBlank()) { + throw new IllegalArgumentException("Unit symbol cannot be blank"); + } + + if (symbol.length() == 1) { + // assume base unit + return baseUnitFor(symbol); + } + + final var prefix = symbol.substring(0, 1); + final var base = symbol.substring(1); + var baseUnit = baseUnits.get(base); + if (baseUnit == null) { + baseUnit = baseUnitFor(base); + } + final var factor = prefixesToFactors.get(prefix); + if (factor == null) { + throw new IllegalArgumentException( + "Unknown unit prefix '" + prefix + "', known prefixes: " + prefixesToFactors.entrySet().stream() + .sorted(Comparator.comparingDouble(Map.Entry::getValue)).map(Map.Entry::getKey).toList()); + } + + unit = new SensorUnit(symbol, baseUnit, factor); + knownUnits.put(symbol, unit); + return unit; } - public static SensorUnit of(String unit) { - Objects.requireNonNull(unit); - return switch (unit) { - case mW -> mWUnit; - case W -> WUnit; - case µJ -> µJUnit; - case decimalPercentage -> decimalPercentageUnit; - default -> new SensorUnit(unit); - }; + private static SensorUnit baseUnitFor(String symbol) { + return baseUnits.computeIfAbsent(symbol, s -> new SensorUnit(symbol, null, 1.0)); } - @SuppressWarnings("unused") - public String getSymbol() { + public boolean isCommensurableWith(SensorUnit other) { + return other != null && base.equals(other.base); + } + + public double conversionFactorTo(SensorUnit other) { + if (other.isCommensurableWith(this)) { + return factor / other.factor; + } else { + throw new IllegalArgumentException("Cannot convert " + this + " to " + other); + } + } + + @JsonProperty("symbol") + public String symbol() { return symbol; } - public Unit getUnit() { - return unit; + public SensorUnit base() { + return base; + } + + public double factor() { + return factor; } @Override @@ -54,17 +122,9 @@ public int hashCode() { return Objects.hashCode(symbol); } - public static final String mW = "mW"; - public static final String W = "W"; - public static final String µJ = "µJ"; - public static final String decimalPercentage = "decimal percentage"; - - private static final SensorUnit mWUnit = new SensorUnit(mW); - private static final SensorUnit WUnit = new SensorUnit(W); - private static final SensorUnit µJUnit = new SensorUnit(µJ); - private static final SensorUnit decimalPercentageUnit = new SensorUnit(decimalPercentage); - - public boolean isWattCommensurable() { - return equals(SensorUnit.WUnit) || unit.compatibleUnits().contains(SensorUnit.WUnit.unit); - } + public static final SensorUnit W = baseUnitFor("W"); + public static final SensorUnit mW = SensorUnit.of("mW"); + public static final SensorUnit J = baseUnitFor("J"); + public static final SensorUnit µJ = SensorUnit.of("µJ"); + public static final SensorUnit decimalPercentage = baseUnitFor("decimal percentage"); } diff --git a/metadata/src/test/java/net/laprun/sustainability/power/SensorMetadataTest.java b/metadata/src/test/java/net/laprun/sustainability/power/SensorMetadataTest.java index d395f78..49b9528 100644 --- a/metadata/src/test/java/net/laprun/sustainability/power/SensorMetadataTest.java +++ b/metadata/src/test/java/net/laprun/sustainability/power/SensorMetadataTest.java @@ -1,6 +1,5 @@ package net.laprun.sustainability.power; -import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import java.util.List; @@ -13,9 +12,9 @@ class SensorMetadataTest { void shouldFailIfTotalComponentsAreOutOfRange() { final var name = "comp0"; final var e = assertThrows(IllegalArgumentException.class, - () -> new SensorMetadata(List.of(new SensorMetadata.ComponentMetadata(name, -1, null, true, null, true)), "")); + () -> new SensorMetadata( + List.of(new SensorMetadata.ComponentMetadata(name, -1, null, true, SensorUnit.W, true)), "")); final var message = e.getMessage(); - assertTrue(message.contains(name) && message.contains("Watts")); assertTrue(message.contains("range")); assertTrue(message.contains("-1")); } @@ -23,18 +22,28 @@ void shouldFailIfTotalComponentsAreOutOfRange() { @Test void shouldFailIfComponentHasNullName() { final var e = assertThrows(IllegalArgumentException.class, - () -> new SensorMetadata(List.of(new SensorMetadata.ComponentMetadata(null, -1, null, true, null, true)), "")); + () -> new SensorMetadata( + List.of(new SensorMetadata.ComponentMetadata(null, -1, null, true, (SensorUnit) null, true)), "")); final var message = e.getMessage(); assertTrue(message.contains("Component name cannot be null")); } + @Test + void shouldFailIfComponentHasNullUnit() { + final var e = assertThrows(IllegalArgumentException.class, + () -> new SensorMetadata( + List.of(new SensorMetadata.ComponentMetadata("invalid", -1, null, true, (SensorUnit) null, true)), "")); + final var message = e.getMessage(); + assertTrue(message.contains("Component unit cannot be null")); + } + @Test void shouldFailOnDuplicatedComponentNames() { final var name = "component"; final var e = assertThrows(IllegalArgumentException.class, () -> new SensorMetadata(List.of( - new SensorMetadata.ComponentMetadata(name, 0, null, true, null, false), - new SensorMetadata.ComponentMetadata(name, 1, null, true, null, false)), "")); + new SensorMetadata.ComponentMetadata(name, 0, null, true, "mW", false), + new SensorMetadata.ComponentMetadata(name, 1, null, true, "mW", false)), "")); final var message = e.getMessage(); assertTrue(message.contains(name) && message.contains("0") && message.contains("1")); } @@ -43,8 +52,8 @@ void shouldFailOnDuplicatedComponentNames() { void shouldFailIfComponentsDoNotCoverFullRange() { final var e = assertThrows(IllegalArgumentException.class, () -> new SensorMetadata(List.of( - new SensorMetadata.ComponentMetadata("foo", 0, null, true, null, false), - new SensorMetadata.ComponentMetadata("component2", 0, null, true, null, false)), "")); + new SensorMetadata.ComponentMetadata("foo", 0, null, true, "mW", false), + new SensorMetadata.ComponentMetadata("component2", 0, null, true, "mW", false)), "")); final var message = e.getMessage(); assertTrue(message.contains("Multiple components are using index 0")); assertTrue(message.contains("foo")); @@ -52,17 +61,6 @@ void shouldFailIfComponentsDoNotCoverFullRange() { assertTrue(message.contains("Missing indices: {1}")); } - @Test - void shouldFailIfTotalComponentsAreNotCommensurateToWatts() { - final var name = "foo"; - final var e = assertThrows(IllegalArgumentException.class, - () -> new SensorMetadata( - List.of(new SensorMetadata.ComponentMetadata(name, 0, "", false, SensorUnit.decimalPercentage, true)), - "")); - final var message = e.getMessage(); - assertTrue(message.contains(name)); - } - @Test void shouldFailIfNoComponentsAreProvided() { final var e = assertThrows(NullPointerException.class, @@ -70,26 +68,4 @@ void shouldFailIfNoComponentsAreProvided() { final var message = e.getMessage(); assertTrue(message.contains("Must provide components")); } - - @Test - void shouldProperlyRecordTotalComponents() { - final String COMPONENT1_NAME = "c1"; - final String COMPONENT2_NAME = "c2"; - final String COMPONENT3_NAME = "c3"; - var metadata = SensorMetadata - .withNewComponent(COMPONENT1_NAME, "component 1", true, "mW", true) - .withNewComponent(COMPONENT2_NAME, "component 2", true, "mW", false) - .withNewComponent(COMPONENT3_NAME, "component 3", true, "mW", true) - .build(); - - assertArrayEquals(new int[] { 0, 2 }, metadata.totalComponents()); - - metadata = SensorMetadata - .withNewComponent("cp1", null, true, null, false) - .withNewComponent("cp2", null, true, null, false) - .withNewComponent("cp3", null, true, null, false) - .build(); - - assertThat(metadata.totalComponents()).isEmpty(); - } } diff --git a/metadata/src/test/java/net/laprun/sustainability/power/SensorUnitTest.java b/metadata/src/test/java/net/laprun/sustainability/power/SensorUnitTest.java new file mode 100644 index 0000000..691d0c5 --- /dev/null +++ b/metadata/src/test/java/net/laprun/sustainability/power/SensorUnitTest.java @@ -0,0 +1,59 @@ +package net.laprun.sustainability.power; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class SensorUnitTest { + + @Test + void ofShouldWork() { + SensorUnit su = SensorUnit.of("mW"); + assertThat(su.symbol()).isEqualTo("mW"); + assertThat(su.base()).isEqualTo(SensorUnit.W); + assertThat(su.factor()).isEqualTo(1e-3); + + su = SensorUnit.of("decimal percentage"); + assertThat(su).isEqualTo(SensorUnit.decimalPercentage); + assertThat(su.factor()).isEqualTo(1); + assertThat(su.base()).isEqualTo(SensorUnit.decimalPercentage); + } + + @Test + void blankSymbolShouldFail() { + final var e = assertThrows(NullPointerException.class, () -> SensorUnit.of(null)); + assertThat(e.getMessage()).contains("Unit symbol cannot be null"); + + final var e2 = assertThrows(IllegalArgumentException.class, () -> SensorUnit.of(" ")); + assertThat(e2.getMessage()).contains("Unit symbol cannot be blank"); + } + + @Test + void unknownPrefixShouldFail() { + final var e = assertThrows(IllegalArgumentException.class, () -> SensorUnit.of("pW")); + assertThat(e.getMessage()).contains("Unknown unit prefix 'p'"); + } + + @Test + void isCommensurableWithShouldWork() { + assertThat(SensorUnit.of("mW").isCommensurableWith(SensorUnit.W)).isTrue(); + assertThat(SensorUnit.of("mW").isCommensurableWith(SensorUnit.J)).isFalse(); + } + + @Test + void conversionFactorShouldWork() { + assertEquals(1, SensorUnit.W.conversionFactorTo(SensorUnit.W)); + assertEquals(1, SensorUnit.J.conversionFactorTo(SensorUnit.J)); + assertEquals(1, SensorUnit.decimalPercentage.conversionFactorTo(SensorUnit.decimalPercentage)); + assertEquals(1, SensorUnit.of("mW").conversionFactorTo(SensorUnit.of("mW"))); + assertEquals(1e-3, SensorUnit.of("mW").conversionFactorTo(SensorUnit.W), 0.0001); + assertEquals(1e3, SensorUnit.of("W").conversionFactorTo(SensorUnit.of("mW")), 0.0001); + assertEquals(1e3, SensorUnit.of("mW").conversionFactorTo(SensorUnit.of("µW")), 0.0001); + assertEquals(1e-3, SensorUnit.of("µW").conversionFactorTo(SensorUnit.of("mW")), 0.0001); + assertEquals(0.1, SensorUnit.of("mW").conversionFactorTo(SensorUnit.of("cW")), 0.0001); + assertEquals(10, SensorUnit.of("cW").conversionFactorTo(SensorUnit.of("mW")), 0.0001); + assertEquals(1e6, SensorUnit.of("MW").conversionFactorTo(SensorUnit.W), 0.0001); + assertEquals(1e-6, SensorUnit.W.conversionFactorTo(SensorUnit.of("MW")), 0.0001); + } +} diff --git a/pom.xml b/pom.xml index b32c9c6..ce5ca46 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,6 @@ 3.17.4 3.26.3 - 4.1.1 3.5.2 3.5.2 @@ -86,11 +85,6 @@ pom import - - eu.hoefel - units - ${units.version} - org.assertj assertj-core diff --git a/server/src/test/java/net/laprun/sustainability/power/PowerResourceTest.java b/server/src/test/java/net/laprun/sustainability/power/PowerResourceTest.java index c3c954e..fe49907 100644 --- a/server/src/test/java/net/laprun/sustainability/power/PowerResourceTest.java +++ b/server/src/test/java/net/laprun/sustainability/power/PowerResourceTest.java @@ -45,6 +45,7 @@ public void testMacOSAppleSiliconMetadataEndpoint() { assertEquals(0, cpu.index()); assertEquals("CPU", cpu.name()); assertEquals(SensorUnit.mW, cpu.unit()); + assertEquals(SensorUnit.W, cpu.unit().base()); assertTrue(cpu.isAttributed()); final var cpuShare = metadata.metadataFor("cpuShare");