Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2890 parameter of cage stack #2892

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
546565f
#2879: Recursion with depth works.
levBagryansky Feb 22, 2024
73e81af
#2890: violations
levBagryansky Feb 22, 2024
773c8f0
#2879: made depth parameter more local
levBagryansky Feb 23, 2024
e44fc05
#2890: Removed cout
levBagryansky Feb 23, 2024
3ee0927
#2890: ConcurrentHashMap
levBagryansky Feb 23, 2024
526bd0f
#2890: No sout. PhTracedEnclosure.this.depth
levBagryansky Feb 25, 2024
6900da7
#2890: Clean it
levBagryansky Feb 25, 2024
facb074
#2890: violations
levBagryansky Feb 25, 2024
f69b70a
#2890: AtomicReference<Integer> counter should not be passed via ctor.
levBagryansky Feb 25, 2024
1a93735
#2890: restart ci
levBagryansky Feb 25, 2024
a200000
#2890: restart ci
levBagryansky Feb 25, 2024
b16672d
#2890: NOT thread-safe
levBagryansky Feb 25, 2024
8bcc3b7
#2890: Disabled the test
levBagryansky Feb 25, 2024
6074c1e
#2890: code style, static constant with class prefix
levBagryansky Feb 26, 2024
500301c
#2890: code style, EOcageTest.writeTo
levBagryansky Feb 26, 2024
2b06737
#2890: RecusionDUmmi -> Dummi
levBagryansky Feb 26, 2024
d0f0b5c
#2890: MAX_CAGE_RECURSION_DEPTH_PROPERTY_NAME
levBagryansky Feb 26, 2024
d952db9
#2890: typo
levBagryansky Feb 26, 2024
0156819
#2890: transferred todo
levBagryansky Feb 26, 2024
12d8f45
#2890: PhTracedEnclosure has a ref to cage instead of its hashcode
levBagryansky Feb 26, 2024
0673dd6
#2890: violation
levBagryansky Feb 26, 2024
dd2952e
#2890: violation
levBagryansky Feb 26, 2024
51ae0b1
#2890: method ref
levBagryansky Feb 26, 2024
bc01d8d
#2890: RecursiveDummy
levBagryansky Feb 26, 2024
e3a9bc7
#2890: lambda
levBagryansky Feb 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.eolang.maven.log.Logs;
import org.eolang.maven.util.HmBase;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

Expand All @@ -49,6 +50,11 @@
* /org/eolang/parser/warnings/mandatory-version-meta.xsl and
* /org/eolang/parser/warnings/mandatory-home-meta.xsl.
* After you need fix {@code createRegEx()}.
* @todo #2890:30min Fix this {@link VerifyMojoTest#detectsErrorsSuccessfully}
* flaky test and enable it. It failed in ci
* <a href="https://github.com/objectionary/eo/actions/runs/8041230784/job/21960239171?pr=2892">here</a>
* without providing the regex and message. Also may be it would be cleaner to fix
* error Assertion since now it is hard to get why it failed.
*/
@SuppressWarnings({"PMD.AvoidDuplicateLiterals", "PMD.TooManyMethods"})
class VerifyMojoTest {
Expand All @@ -64,6 +70,7 @@ void doesNotFailWithNoErrorsAndWarnings(@TempDir final Path temp) {
}

@Test
@Disabled
@CaptureLogs
void detectsErrorsSuccessfully(
@TempDir final Path temp,
Expand Down
2 changes: 1 addition & 1 deletion eo-runtime/src/main/java/EOorg/EOeolang/EOcage.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public EOcage(final Phi sigma) {

@Override
public Phi lambda() {
return new PhTracedEnclosure(this.attr("enclosure").get(), this.hashCode());
return new PhTracedEnclosure(this.attr("enclosure").get(), this);
}

/**
Expand Down
108 changes: 92 additions & 16 deletions eo-runtime/src/main/java/org/eolang/PhTracedEnclosure.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,14 @@
*/
package org.eolang;

import java.util.HashSet;
import java.util.Set;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

/**
* Class to trace if the cage got into recursion during the dataization.
* NOT thread-safe.
* @since 0.36
* @todo #2836:60min Add a new parameter of recursion depth. This parameter
* should be set by user via pom.xml. We can make DATAIZING_CAGES a
* Map and count how many times the cage was met.
* @todo #2836:60min Make the class thread safe. It has private static
* field which can be accessed from differ thread and is not thread safe.
* Needs to synchronize this field.
Expand All @@ -41,10 +39,16 @@
public final class PhTracedEnclosure implements Phi {

/**
* Cages that are currently dataizing. If one cage is datazing and
* Name of property that responsible for keeping max depth.
*/
public static final String
MAX_CAGE_RECURSION_DEPTH_PROPERTY_NAME = "EO_MAX_CAGE_RECURSION_DEPTH";
levBagryansky marked this conversation as resolved.
Show resolved Hide resolved

/**
* Cages that are currently dataizing. If one cage is datazing, and
* it needs to be dataized inside current dataizing, the cage will be here.
*/
private static final Set<Integer> DATAIZING_CAGES = new HashSet<>();
private static final Map<Phi, Integer> DATAIZING_CAGES = new HashMap<>();

/**
* Enclosure.
Expand All @@ -55,16 +59,38 @@ public final class PhTracedEnclosure implements Phi {
* Vertex of cage where the {@link PhTracedEnclosure#enclosure}
* was retrieved.
*/
private final int cage;
private final Phi cage;

/**
* Max depth of cage recursion.
*/
private final int depth;

/**
* Ctor.
* @param enclosure Enclosure.
* @param cage Vertex of source cage.
*/
public PhTracedEnclosure(final Phi enclosure, final int cage) {
public PhTracedEnclosure(final Phi enclosure, final Phi cage) {
this(
enclosure,
cage,
Integer.parseInt(
System.getProperty(PhTracedEnclosure.MAX_CAGE_RECURSION_DEPTH_PROPERTY_NAME, "100")
)
);
}

/**
* The main constructor.
* @param enclosure Enclosure.
* @param cage Cage.
* @param depth Max depth of cage recursion.
*/
public PhTracedEnclosure(final Phi enclosure, final Phi cage, final int depth) {
this.enclosure = enclosure;
this.cage = cage;
this.depth = depth;
}

@Override
Expand Down Expand Up @@ -113,6 +139,7 @@ public boolean equals(final Object obj) {

/**
* Supplier that traces the cage while gets.
* NOT thread-safe.
* @since 0.36
*/
private final class TracingWhileGetting implements Supplier<Attr> {
Expand All @@ -132,16 +159,65 @@ private TracingWhileGetting(final Supplier<Attr> attr) {

@Override
public Attr get() {
if (PhTracedEnclosure.DATAIZING_CAGES.contains(PhTracedEnclosure.this.cage)) {
throw new ExFailure(
"The cage %s is already dataizing",
final Integer incremented = this.incrementCageCounter();
final Attr ret = this.attr.get();
this.decrementCageCounter(incremented);
return ret;
}

/**
* Increments counter of cage in the {@link PhTracedEnclosure#DATAIZING_CAGES}.
* @return New value in the map.
*/
private Integer incrementCageCounter() {
return PhTracedEnclosure.DATAIZING_CAGES.compute(
PhTracedEnclosure.this.cage, (key, counter) -> {
final int ret = this.incremented(counter);
if (ret > PhTracedEnclosure.this.depth) {
throw new ExFailure(
"The cage %s has reached the maximum nesting depth = %d",
key.φTerm(),
levBagryansky marked this conversation as resolved.
Show resolved Hide resolved
PhTracedEnclosure.this.depth
);
}
return ret;
}
);
}

/**
* Creates incremented number.
* @param number Number.
* @return Incremented number. 1 if number is null.
* @checkstyle NonStaticMethodCheck (10 lines). Static declarations in
* inner classes are not supported at language level '8'.
*/
private Integer incremented(final Integer number) {
final int ret;
if (number == null) {
ret = 1;
} else {
ret = number + 1;
}
return ret;
}

/**
* Decrements counter in the {@link PhTracedEnclosure#DATAIZING_CAGES}.
* @param incremented Current value of counter. This argument ensures
* temporal coupling with {@link TracingWhileGetting#incrementCageCounter} method.
*/
private void decrementCageCounter(final int incremented) {
final int decremented = incremented - 1;
if (decremented == 0) {
PhTracedEnclosure.DATAIZING_CAGES.remove(
PhTracedEnclosure.this.cage
);
} else {
PhTracedEnclosure.DATAIZING_CAGES.put(
PhTracedEnclosure.this.cage, decremented
);
}
PhTracedEnclosure.DATAIZING_CAGES.add(PhTracedEnclosure.this.cage);
final Attr ret = this.attr.get();
PhTracedEnclosure.DATAIZING_CAGES.remove(PhTracedEnclosure.this.cage);
return ret;
}
}
}
159 changes: 147 additions & 12 deletions eo-runtime/src/test/java/EOorg/EOeolang/EOcageTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,24 @@
*/
package EOorg.EOeolang;

import java.util.concurrent.atomic.AtomicReference;
import org.eolang.AtFree;
import org.eolang.Atom;
import org.eolang.Data;
import org.eolang.Dataized;
import org.eolang.ExAbstract;
import org.eolang.PhCopy;
import org.eolang.PhDefault;
import org.eolang.PhMethod;
import org.eolang.PhTracedEnclosure;
import org.eolang.PhWith;
import org.eolang.Phi;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

/**
Expand Down Expand Up @@ -256,17 +262,6 @@ void writesBoundedCopyOfTheSameBase() {
);
}

@Test
void throwsExceptionIfRecursion() {
final Phi cage = new EOcage(Phi.Φ);
writeTo(cage, cage);
Assertions.assertThrows(
ExAbstract.class,
new Dataized(cage)::take,
"We expect the exception to be thrown since we have recursion here"
);
}

private static void writeTo(final Phi cage, final Phi obj) {
new Dataized(
new PhWith(
Expand All @@ -277,6 +272,147 @@ private static void writeTo(final Phi cage, final Phi obj) {
).take(Boolean.class);
}

/**
* Cases to test behaviour of cage with recursion.
* @since 0.1
*/
@Nested
class RecursionTests {

/**
* DEPTH.
*/
private static final int MAX_DEPTH = 50;

@BeforeEach
void setDepth() {
System.setProperty(
PhTracedEnclosure.MAX_CAGE_RECURSION_DEPTH_PROPERTY_NAME, String.valueOf(MAX_DEPTH)
);
}

@AfterEach
void clearDepth() {
System.clearProperty(PhTracedEnclosure.MAX_CAGE_RECURSION_DEPTH_PROPERTY_NAME);
}

@Test
void throwsExceptionIfRecursion() {
final Phi cage = new EOcage(Phi.Φ);
writeTo(cage, cage);
Assertions.assertThrows(
ExAbstract.class,
() -> new Dataized(cage).take(),
"We expect the exception to be thrown since we have recursion here"
);
}

@Test
void doesNotThrowExceptionIfSmallDepth() {
final EOcage cage = new EOcage(Phi.Φ);
EOcageTest.writeTo(
cage,
new RecursiveDummy(EOcageTest.RecursionTests.MAX_DEPTH / 2, cage)
);
Assertions.assertDoesNotThrow(
() -> new Dataized(cage).take(),
levBagryansky marked this conversation as resolved.
Show resolved Hide resolved
String.format(
"We expect that dataizing of nested cage which recursion depth is less than property %s = %s does not throw %s",
PhTracedEnclosure.MAX_CAGE_RECURSION_DEPTH_PROPERTY_NAME,
System.getProperty(PhTracedEnclosure.MAX_CAGE_RECURSION_DEPTH_PROPERTY_NAME),
ExAbstract.class
)
);
}

/**
* The boundary case when the depth is equal to the maximum allowed.
*/
@Test
void doesNotThrowExceptionIfMaxDepth() {
final EOcage cage = new EOcage(Phi.Φ);
writeTo(
cage,
new RecursiveDummy(MAX_DEPTH, cage)
);
Assertions.assertDoesNotThrow(
() -> new Dataized(cage).take(),
levBagryansky marked this conversation as resolved.
Show resolved Hide resolved
String.format(
"We expect that dataizing of nested cage which recursion depth is equal to property %s = %s does not throw %s",
PhTracedEnclosure.MAX_CAGE_RECURSION_DEPTH_PROPERTY_NAME,
System.getProperty(PhTracedEnclosure.MAX_CAGE_RECURSION_DEPTH_PROPERTY_NAME),
ExAbstract.class
)
);
}

@Test
void throwsExceptionIfBigDepth() {
final EOcage cage = new EOcage(Phi.Φ);
writeTo(
cage,
new RecursiveDummy(EOcageTest.RecursionTests.MAX_DEPTH + 1, cage)
);
Assertions.assertThrows(
ExAbstract.class,
() -> new Dataized(cage).take(),
levBagryansky marked this conversation as resolved.
Show resolved Hide resolved
String.format(
"We expect that dataizing of nested cage which recursion depth is more than property %s = %s does not throw %s",
PhTracedEnclosure.MAX_CAGE_RECURSION_DEPTH_PROPERTY_NAME,
System.getProperty(PhTracedEnclosure.MAX_CAGE_RECURSION_DEPTH_PROPERTY_NAME),
ExAbstract.class
)
);
}

/**
* Recursive {@link Phi}.
* @since 0.1
*/
private final class RecursiveDummy extends PhDefault implements Atom {

/**
* How many times should we met the cage while dataizing it eventually.
*/
private final int depth;

/**
* The cage.
*/
private final EOcage cage;

/**
* Counts how many times we already met the cage.
*/
private final AtomicReference<Integer> counter;

/**
* Ctor.
* @param depth Depth.
* @param cage Cage.
*/
RecursiveDummy(
final int depth, final EOcage cage
) {
this.depth = depth;
this.cage = cage;
this.counter = new AtomicReference<>(0);
}

@Override
public Phi lambda() {
final Phi ret;
this.counter.getAndUpdate(val -> val + 1);
if (this.counter.get() == this.depth) {
ret = new Data.ToPhi(0L);
} else {
ret = this.cage;
}
return ret;
}
}
}

/**
* Dummy Phi.
* @since 1.0
Expand All @@ -291,5 +427,4 @@ public static final class Dummy extends PhDefault {
this.add("x", new AtFree());
}
}

}
Loading