/* * ARX: Powerful Data Anonymization * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors * * 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.deidentifier.arx.test; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.util.Arrays; import org.deidentifier.arx.ARXAnonymizer; import org.deidentifier.arx.ARXLattice; import org.deidentifier.arx.ARXLattice.ARXNode; import org.deidentifier.arx.ARXResult; import org.deidentifier.arx.Data; import org.deidentifier.arx.metric.InformationLoss; import org.junit.Test; /** * Test for utility metrics. * * @author Fabian Prasser * @author Florian Kohlmayer */ public abstract class AbstractTestUtilityEstimation extends AbstractTestUtilityMetrics { /** * Creates a new instance. * * @param testcase */ public AbstractTestUtilityEstimation(final ARXUtilityMetricsTestCase testcase) { super(testcase); } @Test public void test() throws IOException { // Anonymize Data data = getDataObject(testcase); ARXAnonymizer anonymizer = new ARXAnonymizer(); // With suppression for anonymous transformation ARXResult result = anonymizer.anonymize(data, testcase.config); checkResult(testcase, result); data.getHandle().release(); // Without suppression for non-anonymous transformations testcase.config.setSuppressionAlwaysEnabled(false); result = anonymizer.anonymize(data, testcase.config); checkResult(testcase, result); } /** * Tests all estimates within a lattice. * * @param lattice */ private void checkLattice(ARXLattice lattice) { for (ARXNode[] level : lattice.getLevels()) { for (ARXNode node : level) { assertTrue("Min > max", compareWithTolerance(node.getLowestScore(), node.getHighestScore()) <= 0); } } } /** * Tests the result. * * @param testcase * @param result */ private void checkResult(ARXUtilityMetricsTestCase testcase, ARXResult result) { // First check the initial estimates checkLattice(result.getLattice()); // Test estimates after checking some transformations for (ARXNode[] level : result.getLattice().getLevels()) { for (ARXNode node : level) { String label = Arrays.toString(node.getTransformation()); if (testcase.informationLoss.containsKey(label)) { if (compareWithTolerance(node.getHighestScore(), node.getLowestScore()) != 0) { // Check transformation and test bounds checkTransformation(testcase, result, node); // Check the whole lattice checkLattice(result.getLattice()); } } } } } /** * Applies and checks a transformation. * * @param testcase * @param result * @param node */ private void checkTransformation(ARXUtilityMetricsTestCase testcase, ARXResult result, ARXNode node) { InformationLoss<?> min = node.getLowestScore(); InformationLoss<?> max = node.getHighestScore(); result.getOutput(node, false); assertTrue("Min != max", compareWithTolerance(node.getLowestScore(), node.getHighestScore()) == 0); assertTrue("Actual < min", compareWithTolerance(min, node.getHighestScore()) <= 0); assertTrue("Actual > max", compareWithTolerance(max, node.getHighestScore()) >= 0); } /** * Compares double for "equality" with a tolerance of 1 ulp. * * @param d1 * @param d2 * @return */ private boolean closeEnough(double d1, double d2) { return Math.abs(d2 - d1) <= Math.max(Math.ulp(d1), Math.ulp(d2)); } /** * Compares two doubles with tolerance. * * @param d1 * @param d2 * @return */ private int compareWithTolerance(double d1, double d2) { if (closeEnough(d1, d2)) return 0; else return Double.compare(d1, d2); } /** * Compares two losses with tolerance. * * @param loss1 * @param loss2 * @return */ private int compareWithTolerance(InformationLoss<?> loss1, InformationLoss<?> loss2) { String s1 = loss1.toString(); String s2 = loss2.toString(); if (isNumeric(s1)) { Double d1 = Double.valueOf(s1); Double d2 = Double.valueOf(s2); return compareWithTolerance(d1, d2); } else { return loss1.compareTo(loss2); } } /** * Returns whether the given string is numeric. * * @param str * @return */ private boolean isNumeric(String str) { return str.matches("-?\\d+(\\.\\d+)?"); } }