From 5702a7e538befdc659f83fc8a75ca4e6fdcafd95 Mon Sep 17 00:00:00 2001 From: Enrico Date: Mon, 3 Oct 2022 11:40:31 +0200 Subject: [PATCH 1/2] feat: PredictableRandomizer will create random values based on a seed [TECH-1449] --- pom.xml | 2 +- .../hisp/dhis/utils/JsonObjectBuilder.java | 2 +- .../dhis/utils/PredictableRandomizer.java | 189 +++++++++++++++++ .../java/org/hisp/dhis/utils/Randomizer.java | 199 ++++++++++++++++++ .../dhis/utils/PredictableRandomizerTest.java | 73 +++++++ 5 files changed, 463 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/hisp/dhis/utils/PredictableRandomizer.java create mode 100644 src/main/java/org/hisp/dhis/utils/Randomizer.java create mode 100644 src/test/java/org/hisp/dhis/utils/PredictableRandomizerTest.java diff --git a/pom.xml b/pom.xml index 309b1dc..6c5be98 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ api-test-utils org.hisp.dhis api-test-utils - 1.1.7 + 1.1.8 jar https://github.com/dhis2/api-test-utils API test utils diff --git a/src/main/java/org/hisp/dhis/utils/JsonObjectBuilder.java b/src/main/java/org/hisp/dhis/utils/JsonObjectBuilder.java index 5e93e3b..17f2b4a 100644 --- a/src/main/java/org/hisp/dhis/utils/JsonObjectBuilder.java +++ b/src/main/java/org/hisp/dhis/utils/JsonObjectBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2020, University of Oslo + * Copyright (c) 2004-2022, University of Oslo * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/src/main/java/org/hisp/dhis/utils/PredictableRandomizer.java b/src/main/java/org/hisp/dhis/utils/PredictableRandomizer.java new file mode 100644 index 0000000..6382ff6 --- /dev/null +++ b/src/main/java/org/hisp/dhis/utils/PredictableRandomizer.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2004-2022, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.utils; + +import com.github.javafaker.Faker; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * This Randomizer will return random values based on a seed. + * Using the same seed will make the Randomizer to generate + * the same sequence of random values. + */ +public class PredictableRandomizer implements Randomizer { + + private final Random rnd; + + private final Faker faker; + + public PredictableRandomizer( long seed ) + { + this.rnd = new Random( seed ); + this.faker = new Faker( rnd ); + } + + @Override + public Random getRandom() { + return this.rnd; + } + + @Override + public int randomInt(int bound) + { + return rnd.nextInt(bound); + } + + @Override + public List randomElementsFromList( List list, int size ) + { + List copiedList = new ArrayList<>(list); + List resultList = new ArrayList<>(); + + for ( int i = 0; i < size && !copiedList.isEmpty(); i++) { + T randomElementFromList = randomElementFromList( copiedList ); + resultList.add( copiedList.get( copiedList.indexOf( randomElementFromList ) ) ); + copiedList.remove( randomElementFromList ); + } + + return resultList; + } + + @Override + public String randomString() + { + return this.faker.programmingLanguage().name(); + } + + public String randomString( int size ) + { + return this.faker.lorem().characters( size, true, false ); + } + + public int randomIntInRange( int min, int max ) + { + return this.faker.number().numberBetween( min, max ); + } + + @Override + public double randomDoubleInRange( int min, int max, int decimals ) + { + return this.faker.number().randomDouble( decimals, min, max ); + } + + @Override + public boolean randomBoolean() + { + return this.faker.bool().bool(); + } + + @Override + public String randomUsername() { + return this.faker.name().username(); + } + + @Override + public String randomFirstName() { + return this.faker.name().firstName(); + } + + @Override + public String randomLastName() { + return this.faker.name().lastName(); + } + + @Override + public String randomAddress() { + return this.faker.address().fullAddress(); + } + + @Override + public String randomNationalId() { + return this.faker.idNumber().valid(); + } + + @Override + public Date randomBirthday() { + return this.faker.date().birthday( 18, 80 ); + } + + @Override + public String randomLongText( int wordCount ) { + return this.faker.lorem().sentence( wordCount ); + } + + @Override + public String randomPhoneNumber() { + return this.faker.phoneNumber().phoneNumber(); + } + + @Override + public Date randomFutureDate() + { + return this.faker.date().future( 1825, TimeUnit.DAYS ); + } + + @Override + public String randomFutureDate( DateTimeFormatter formatter ) + { + return formatDate( toLocalDate( randomFutureDate() ), formatter ); + } + + @Override + public Date randomPastDate() + { + return this.faker.date().past( 1825, TimeUnit.DAYS ); + } + + @Override + public String randomPastDate( DateTimeFormatter formatter ) + { + return formatDate( toLocalDate( randomPastDate() ), formatter ); + } + + @Override + public String randomDate(DateTimeFormatter formatter ) + { + return formatDate( toLocalDate( getDate() ), formatter ); + } + + private String formatDate(LocalDate localDate, DateTimeFormatter format ) + { + return localDate.format( format ); + } + + private LocalDate toLocalDate( Date dateToConvert ) + { + return dateToConvert.toInstant().atZone( ZoneId.systemDefault() ).toLocalDate(); + } +} diff --git a/src/main/java/org/hisp/dhis/utils/Randomizer.java b/src/main/java/org/hisp/dhis/utils/Randomizer.java new file mode 100644 index 0000000..063f802 --- /dev/null +++ b/src/main/java/org/hisp/dhis/utils/Randomizer.java @@ -0,0 +1,199 @@ +package org.hisp.dhis.utils; + +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Random; + +public interface Randomizer { + /** + * Extracts a random element from a list + * + * @param list a List + * @param type + * @return a random element from the list + */ + default T randomElementFromList( List list ) + { + return list.get( randomInt( list.size() ) ); + } + + /** + * Return the underlying {@link Random } object + * + * @return a {@link Random } object + */ + Random getRandom(); + + /** + * Generates a random integer + * + * @param bound the maximum boundary for the generated int + * @return a random int + */ + public int randomInt(int bound); + + /** + * Extracts a random sublist of n elements from a list + * + * @param list a List + * @param size the size of the sublist + * @param type + * @return a random sublist + */ + List randomElementsFromList(List list, int size); + + /** + * Returns random string containing 6 alphabetical characters. + * + * @return a String + */ + String randomString(); + + /** + * Returns random string of alphabetical characters. + * + * @param size the size of the string + * @return a String + */ + String randomString( int size ); + + /** + * Generates a random integer + * + * @param min the minimum boundary for the generated int + * @param max the maximum boundary for the generated int + * @return a random int + */ + int randomIntInRange( int min, int max ); + + /** + * Generates a random integer + * + * @param decimals maximum number of places + * @param min minimum value + * @param max maximum value + * @return a double + */ + double randomDoubleInRange( int min, int max, int decimals ); + + /** + * Generates random boolean value + * + * @return a boolean + */ + boolean randomBoolean(); + + /** + * Generates random username + * + * @return a username + */ + String randomUsername(); + + /** + * Generates random first name + * + * @return a first name + */ + String randomFirstName(); + + /** + * Generates random last name + * + * @return a last name + */ + String randomLastName(); + + /** + * Generates random address + * + * @return a address + */ + String randomAddress(); + + /** + * Generates random national id + * + * @return a national id + */ + String randomNationalId(); + + /** + * Generates random birthday date + * + * @return a birthday date + */ + Date randomBirthday(); + + /** + * Generates random long text + * + * @param wordCount number of words in the text + * @return a long text + */ + String randomLongText(int wordCount ); + + /** + * Generates random phone number + * + * @return a phone number + */ + String randomPhoneNumber(); + + /** + * Generates a random date in the future. The date is maximum 5 years from now + * + * @return a Date + */ + Date randomFutureDate(); + + /** + * Generates a random date in the future. The date is maximum 5 years from now + * + * @param formatter a {DateTimeFormatter} for formatting the date + * @return a Date formatted according to the specified {DateTimeFormatter} + */ + String randomFutureDate( DateTimeFormatter formatter ); + + /** + * Generates a random date in the past. The date is maximum 5 years in the past + * from now + * + * @return a Date + */ + Date randomPastDate(); + + /** + * Generates a random date in the past. The date is maximum 5 years in the past + * from now + * + * @param formatter a {DateTimeFormatter} for formatting the date + * @return a Date formatted according to the specified {DateTimeFormatter} + */ + String randomPastDate( DateTimeFormatter formatter ); + + /** + * Generates a random date The date is maximum 5 years in the past from now or 5 + * years in the future from now + * + * @return a Date + */ + default Date getDate() + { + List dates = new ArrayList<>(); + dates.add( randomPastDate() ); + dates.add( randomFutureDate() ); + return randomElementFromList( dates ); + } + + /** + * Generates a random date The date is maximum 5 years in the past from now or 5 + * years in the future from now + * + * @param formatter a DateTimeFormatter + * @return a Date formatted according to the specified DateTimeFormatter + */ + String randomDate( DateTimeFormatter formatter ); +} diff --git a/src/test/java/org/hisp/dhis/utils/PredictableRandomizerTest.java b/src/test/java/org/hisp/dhis/utils/PredictableRandomizerTest.java new file mode 100644 index 0000000..21b6dea --- /dev/null +++ b/src/test/java/org/hisp/dhis/utils/PredictableRandomizerTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2004-2022, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.utils; + +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +public class PredictableRandomizerTest { + + @Test + public void testSameSeedCreateSameRandomSequence(){ + PredictableRandomizer random1 = new PredictableRandomizer(1L); + PredictableRandomizer random2 = new PredictableRandomizer(1L); + + List random1Sequence = IntStream.range(0, 100) + .mapToObj(o -> random1.randomInt(100)) + .collect(Collectors.toList()); + + List random2Sequence = IntStream.range(0, 100) + .mapToObj(o -> random2.randomInt(100)) + .collect(Collectors.toList()); + + assertEquals( random1Sequence, random2Sequence ); + } + + @Test + public void testDifferentSeedCreateDifferentRandomSequence(){ + PredictableRandomizer random1 = new PredictableRandomizer(1L); + PredictableRandomizer random2 = new PredictableRandomizer(2L); + + List random1Sequence = IntStream.range(0, 100) + .mapToObj(o -> random1.randomInt(100)) + .collect(Collectors.toList()); + + List random2Sequence = IntStream.range(0, 100) + .mapToObj(o -> random2.randomInt(100)) + .collect(Collectors.toList()); + + assertNotEquals( random1Sequence, random2Sequence ); + } +} From 5ff716c218198a94202b0ce92b09dc64546a8862 Mon Sep 17 00:00:00 2001 From: Enrico Date: Thu, 13 Oct 2022 15:26:05 +0200 Subject: [PATCH 2/2] Fix code review comments --- .../org/hisp/dhis/utils/PredictableRandomizer.java | 7 ++++++- src/main/java/org/hisp/dhis/utils/Randomizer.java | 12 ++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/hisp/dhis/utils/PredictableRandomizer.java b/src/main/java/org/hisp/dhis/utils/PredictableRandomizer.java index 6382ff6..8b1072b 100644 --- a/src/main/java/org/hisp/dhis/utils/PredictableRandomizer.java +++ b/src/main/java/org/hisp/dhis/utils/PredictableRandomizer.java @@ -133,10 +133,15 @@ public String randomNationalId() { } @Override - public Date randomBirthday() { + public Date randomAdultBirthday() { return this.faker.date().birthday( 18, 80 ); } + @Override + public Date randomBirthday(int minAge, int maxAge){ + return this.faker.date().birthday( minAge, maxAge ); + } + @Override public String randomLongText( int wordCount ) { return this.faker.lorem().sentence( wordCount ); diff --git a/src/main/java/org/hisp/dhis/utils/Randomizer.java b/src/main/java/org/hisp/dhis/utils/Randomizer.java index 063f802..cdaf37c 100644 --- a/src/main/java/org/hisp/dhis/utils/Randomizer.java +++ b/src/main/java/org/hisp/dhis/utils/Randomizer.java @@ -45,7 +45,7 @@ default T randomElementFromList( List list ) List randomElementsFromList(List list, int size); /** - * Returns random string containing 6 alphabetical characters. + * Returns random string. * * @return a String */ @@ -125,7 +125,15 @@ default T randomElementFromList( List list ) * * @return a birthday date */ - Date randomBirthday(); + Date randomAdultBirthday(); + + /** + * Generates random birthday date with age + * between minAge and maxAge + * + * @return a birthday date + */ + Date randomBirthday(int minAge, int maxAge); /** * Generates random long text