Skip to content

Commit

Permalink
Change regexp generation library to RgxGen
Browse files Browse the repository at this point in the history
  • Loading branch information
SooKim1110 committed Jan 12, 2024
1 parent f615f30 commit 4d02923
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 61 deletions.
3 changes: 2 additions & 1 deletion fixture-monkey-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ dependencies {
compileOnly("net.jqwik:jqwik-api:${Versions.JQWIK}")
compileOnly("net.jqwik:jqwik-web:${Versions.JQWIK}")
compileOnly("net.jqwik:jqwik-time:${Versions.JQWIK}")
compileOnly("com.github.mifmif:generex:1.0.2")
compileOnly("com.github.curious-odd-man:rgxgen:1.4")

testImplementation("net.jqwik:jqwik-engine:${Versions.JQWIK}")
testImplementation("net.jqwik:jqwik-api:${Versions.JQWIK}")
Expand All @@ -37,6 +37,7 @@ dependencies {
testImplementation("org.projectlombok:lombok:${Versions.LOMBOK}")
testImplementation("org.assertj:assertj-core:${Versions.ASSERTJ}")
testImplementation("net.jqwik:jqwik-time:${Versions.JQWIK}")
testImplementation("com.github.curious-odd-man:rgxgen:1.4")
testAnnotationProcessor("org.projectlombok:lombok:${Versions.LOMBOK}")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,80 +18,85 @@

package com.navercorp.fixturemonkey.api.random;

import static java.util.stream.Collectors.toList;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import javax.annotation.Nullable;

import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mifmif.common.regex.Generex;
import com.github.curiousoddman.rgxgen.RgxGen;
import com.github.curiousoddman.rgxgen.config.RgxGenOption;
import com.github.curiousoddman.rgxgen.config.RgxGenProperties;
import com.github.curiousoddman.rgxgen.iterators.StringIterator;

import dk.brics.automaton.RegExp;
import com.navercorp.fixturemonkey.api.type.TypeCache;

@API(since = "0.6.9", status = Status.MAINTAINED)
public final class RegexGenerator {
private static final Map<String, String> PREDEFINED_CHARACTER_CLASSES;

static {
Map<String, String> characterClasses = new HashMap<>();
characterClasses.put("\\\\d", "[0-9]");
characterClasses.put("\\\\D", "[^0-9]");
characterClasses.put("\\\\s", "[ \t\n\f\r]");
characterClasses.put("\\\\S", "[^ \t\n\f\r]");
characterClasses.put("\\\\w", "[a-zA-Z_0-9]");
characterClasses.put("\\\\W", "[^a-zA-Z_0-9]");
PREDEFINED_CHARACTER_CLASSES = Collections.unmodifiableMap(characterClasses);
}
private static final Logger LOGGER = LoggerFactory.getLogger(TypeCache.class);
public static final int DEFAULT_REGEXP_GENERATION_TIMEOUT_SEC = 10;
public static final int DEFAULT_REGEXP_GENERATION_MAX_SIZE = 100;

public List<String> generateAll(String regex, int[] flags, @Nullable Integer min, @Nullable Integer max) {
for (Map.Entry<String, String> charClass : PREDEFINED_CHARACTER_CLASSES.entrySet()) {
regex = regex.replaceAll(charClass.getKey(), charClass.getValue());
}

RegExp regExp;
if (flags.length == 0) {
regExp = new RegExp(regex);
} else {
int intFlag = 0;
for (int flag : flags) {
intFlag = intFlag | flag;
}
regExp = new RegExp(regex, intFlag);
ExecutorService executor = Executors.newSingleThreadExecutor();

try {
RgxGen rgxGen = generateRgxGen(regex, flags);

int minLength = min == null ? 0 : min;
int maxLength = max == null ? Integer.MAX_VALUE : max;

List<String> result = new ArrayList<>();

Future<?> future = executor.submit(() -> {
StringIterator stringIterator = rgxGen.iterateUnique();
Spliterator<String> spliterator =
Spliterators.spliteratorUnknownSize(stringIterator, Spliterator.ORDERED);

result.addAll(StreamSupport.stream(spliterator, false)
.filter(it -> it.length() >= minLength && it.length() <= maxLength)
.limit(DEFAULT_REGEXP_GENERATION_MAX_SIZE)
.collect(Collectors.toList()));
});

future.get(DEFAULT_REGEXP_GENERATION_TIMEOUT_SEC, TimeUnit.SECONDS);

Collections.shuffle(result);
return result;
} catch (Exception ex) {
throw new IllegalArgumentException(
String.format(
"String generation timed out for the regular expression \"%s\" provided in @Pattern."
+ " Either the regular expression is incorrect, or it takes too much time to generate",
regex
)
);
} finally {
executor.shutdown();
}

Generex generex = new Generex(regExp.toAutomaton());
return this.generateAll(generex, min, max);
}

public List<String> generateAll(String regex) {
return this.generateAll(regex, null, null);
}

public List<String> generateAll(String regex, @Nullable Integer min, @Nullable Integer max) {
return this.generateAll(new Generex(regex), min, max);
}

private List<String> generateAll(Generex generex, @Nullable Integer min, @Nullable Integer max) {
if (min == null) {
min = 0;
private static RgxGen generateRgxGen(String regex, int[] flags) {
RgxGenProperties properties = new RgxGenProperties();
if (Arrays.stream(flags).anyMatch(it -> it == 2)) {
RgxGenOption.CASE_INSENSITIVE.setInProperties(properties, true);
}

if (max == null) {
max = 255;
}

Integer regexMin = min;
Integer regexMax = max;
List<String> result = generex.getMatchedStrings(100).stream()
.filter(it -> it.length() >= regexMin && it.length() <= regexMax)
.collect(toList());
Collections.shuffle(result);
return result;
RgxGen rgxGen = new RgxGen(regex);
rgxGen.setProperties(properties);
return rgxGen;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.navercorp.fixturemonkey.api.random;

import static org.assertj.core.api.BDDAssertions.then;
import static org.assertj.core.api.BDDAssertions.thenThrownBy;

import java.util.List;
import java.util.regex.Pattern;

import org.junit.jupiter.api.Test;

public class RegexGeneratorTest {
private static final RegexGenerator SUT = new RegexGenerator();
private static final int FLAG_CASE_INSENSITIVE = 2;

@Test
void regExpGenerationSuccess() {
String emailRegex = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";
List<String> strings = SUT.generateAll(emailRegex, new int[] {}, null, null);

Pattern pattern = Pattern.compile(emailRegex);
then(strings).allMatch(it -> pattern.matcher(it).matches());
}

@Test
void generateRegExpWithCaseInsensitiveFlag() {
List<String> strings = SUT.generateAll("a", new int[] {FLAG_CASE_INSENSITIVE}, null, null);

then("a").isIn(strings);
then("A").isIn(strings);
}

@Test
void generateRegExpWithMinMaxLength() {
List<String> strings = SUT.generateAll("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", new int[] {}, 3, 7);

then(strings).allMatch(it -> it.length() >= 3 && it.length() <= 7);
}

@Test
void generateInvalidRegExpThrows() {
thenThrownBy(
() -> SUT.generateAll("^^a", new int[] {}, null, null)
).isExactlyInstanceOf(IllegalArgumentException.class);
}

@Test
void generateIncalculableRegExpThrows() {
thenThrownBy(
() -> SUT.generateAll(
"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
new int[] {},
20,
null
)
).isExactlyInstanceOf(IllegalArgumentException.class);
}
}
2 changes: 1 addition & 1 deletion fixture-monkey/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ dependencies {
api(project(":fixture-monkey-api"))

api("net.jqwik:jqwik:${Versions.JQWIK}")
api("com.github.mifmif:generex:1.0.2")
api("com.github.curious-odd-man:rgxgen:1.4")

testRuntimeOnly(project(":fixture-monkey-engine"))
testImplementation("org.junit.jupiter:junit-jupiter-engine:${Versions.JUNIT_JUPITER}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public T combined() {
throw new IllegalArgumentException(
String.format(
"Given type %s could not be generated."
+ "Check the ArbitraryIntrospector used or the APIs used in the ArbitraryBuilder..",
+ " Check the ArbitraryIntrospector used or the APIs used in the ArbitraryBuilder.",
rootProperty.getType()
),
lastException
Expand All @@ -115,7 +115,7 @@ public Object rawValue() {
throw new IllegalArgumentException(
String.format(
"Given type %s could not be generated."
+ "Check the ArbitraryIntrospector used or the APIs used in the ArbitraryBuilder.",
+ " Check the ArbitraryIntrospector used or the APIs used in the ArbitraryBuilder.",
rootProperty.getType()
),
lastException
Expand Down

0 comments on commit 4d02923

Please sign in to comment.