Skip to content

Commit

Permalink
#3461 Expect is stronger
Browse files Browse the repository at this point in the history
  • Loading branch information
yegor256 committed Nov 24, 2024
1 parent 8e08f74 commit 1e6f5da
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 35 deletions.
21 changes: 19 additions & 2 deletions eo-runtime/src/main/java/EOorg/EOeolang/EObytes$EOslice.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.eolang.Attr;
import org.eolang.Data;
import org.eolang.Dataized;
import org.eolang.Expect;
import org.eolang.PhDefault;
import org.eolang.Phi;
import org.eolang.Versionized;
Expand All @@ -60,8 +61,24 @@ public final class EObytes$EOslice extends PhDefault implements Atom {

@Override
public Phi lambda() {
final int start = new Dataized(this.take("start")).asNumber().intValue();
final int length = new Dataized(this.take("len")).asNumber().intValue();
final int start = Expect.at(this, "start")
.that(phi -> new Dataized(phi).asNumber())
.otherwise("must be a number")
.must(number -> number % 1 == 0)
.that(Double::intValue)
.otherwise("must be an integer")
.must(integer -> integer >= 0)
.otherwise("must be a positive integer")
.it();
final int length = Expect.at(this, "len")
.that(phi -> new Dataized(phi).asNumber())
.otherwise("must be a number")
.must(number -> number % 1 == 0)
.that(Double::intValue)
.otherwise("must be an integer")
.must(integer -> integer >= 0)
.otherwise("must be a positive integer")
.it();
final byte[] array = new Dataized(this.take(Attr.RHO)).take();
return new Data.ToPhi(
Arrays.copyOfRange(array, start, start + length)
Expand Down
7 changes: 3 additions & 4 deletions eo-runtime/src/main/java/EOorg/EOeolang/EOnumber$EOtimes.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,9 @@ public final class EOnumber$EOtimes extends PhDefault implements Atom {
@Override
public Phi lambda() {
final Double left = new Dataized(this.take(Attr.RHO)).asNumber();
final Double right = new Expect<>(
new Dataized(this.take("x"))::asNumber,
"number.times expects its argument to be a number"
).it();
final Double right = Expect.at(this, "x")
.that(phi -> new Dataized(phi).asNumber())
.it();
return new Data.ToPhi(left * right);
}
}
17 changes: 8 additions & 9 deletions eo-runtime/src/main/java/EOorg/EOeolang/EOtxt/EOsprintf.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,14 @@ public EOsprintf() {
@Override
public Phi lambda() {
final String format = new Dataized(this.take("format")).asString();
final Phi args = this.take("args");
final Phi retriever = new Expect<>(
() -> args.take("at"),
"sprintf expects its second argument to be a tuple with the 'at' attribute"
).it();
final long length = new Expect<>(
() -> new Dataized(args.take("length")).asNumber().longValue(),
"sprintf expects its second argument to be a tuple with the 'length' attribute"
).it();
final Phi retriever = Expect.at(this, "args")
.that(phi -> phi.take("at"))
.otherwise("be a tuple with the 'at' attribute")
.it();
final long length = Expect.at(this, "args")
.that(phi -> new Dataized(phi.take("length")).asNumber().intValue())
.otherwise("be a tuple with the 'length' attribute")
.it();
final List<Object> arguments = new ArrayList<>(0);
String pattern = format;
long index = 0;
Expand Down
107 changes: 87 additions & 20 deletions eo-runtime/src/main/java/org/eolang/Expect.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,46 +24,113 @@

package org.eolang;

import java.util.function.Function;
import java.util.function.Supplier;

/**
* This wrapper helps us explain our expectations in an error
* message that we throw.
*
* @param <T> Type of returned value
* @param <T> The type of result
* @since 0.41.0
*/
public final class Expect<T> {
@SuppressWarnings("PMD.ShortMethodName")
public class Expect<T> {

/**
* The action.
* The subject being tested.
*/
private final Action<T> action;
private final String subject;

/**
* The message.
* The supplier.
*/
private final String message;
private final Supplier<T> sup;

/**
* Ctor.
* @param act The action
* @param msg Additional explanation
* @param subj The subject
* @param supplier The supplier
*/
public Expect(final String subj, final Supplier<T> supplier) {
this.subject = subj;
this.sup = supplier;
}

/**
* Starting point.
* @param phi The object
* @param attr Attribute name
* @return Expect pipeline
* @checkstyle MethodNameCheck (5 lines)
*/
@SuppressWarnings("PMD.ProhibitPublicStaticMethods")
public static Expect<Phi> at(final Phi phi, final String attr) {
return new Expect<>(
String.format("the '%s' attribute", attr),
() -> phi.take(attr)
);
}

/**
* Assert that it passes.
* @param fun The function to transform
* @param <R> Type of result
* @return New object
*/
public Expect(final Action<T> act, final String msg) {
this.action = act;
this.message = msg;
public <R> Expect<R> that(final Function<T, R> fun) {
return new Expect<>(
this.subject,
() -> fun.apply(this.sup.get())
);
}

/**
* Take the value from the lambda.
* @return The value
* @checkstyle MethodNameCheck (3 lines)
* Fail with this message otherwise.
* @param message The error message
* @return Next object
*/
public Expect<T> otherwise(final String message) {
return new Expect<>(
this.subject,
() -> {
try {
return this.sup.get();
} catch (final ExFailure ex) {
throw new ExFailure(
String.format("%s %s", this.subject, message),
ex
);
}
}
);
}

/**
* Assert on it.
* @param fun The check.
* @return Next object
*/
public Expect<T> must(final Function<T, Boolean> fun) {
return new Expect<>(
this.subject,
() -> {
final T ret = this.sup.get();
if (!fun.apply(ret)) {
throw new ExFailure(this.subject);
}
return ret;
}
);
}

/**
* Return it.
* @return The token
* @checkstyle MethodNameCheck (5 lines)
*/
@SuppressWarnings("PMD.ShortMethodName")
public T it() {
try {
return this.action.act();
} catch (final ExFailure ex) {
throw new ExFailure(this.message, ex);
}
return this.sup.get();
}

}
103 changes: 103 additions & 0 deletions eo-runtime/src/test/java/EOorg/EOeolang/EObytesEOsliceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016-2024 Objectionary.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

/*
* @checkstyle PackageNameCheck (10 lines)
* @checkstyle TrailingCommentCheck (3 lines)
*/
package EOorg.EOeolang; // NOPMD

import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import org.eolang.Data;
import org.eolang.Dataized;
import org.eolang.ExFailure;
import org.eolang.PhWith;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

/**
* Test case for {@link EObytes}.
*
* @since 0.23
* @checkstyle TypeNameCheck (4 lines)
*/
final class EObytesEOsliceTest {

@Test
void takesLegalSlice() {
MatcherAssert.assertThat(
"slice is taken correctly",
new Dataized(
new PhWith(
new PhWith(
new Data.ToPhi("hello, world!")
.take("as-bytes")
.take("slice")
.copy(),
"start",
new Data.ToPhi(2)
),
"len",
new Data.ToPhi(5)
)
).asString(),
Matchers.equalTo("llo, ")
);
}

@Test
void takesWrongSlice() {
final ExFailure exp = Assertions.assertThrows(
ExFailure.class,
() -> new Dataized(
new PhWith(
new PhWith(
new Data.ToPhi("hello, world!")
.take("as-bytes")
.take("slice")
.copy(),
"start",
new Data.ToPhi(2)
),
"len",
new Data.ToPhi(-5)
)
).asString(),
"fails on check"
);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (PrintWriter writer = new PrintWriter(baos)) {
exp.printStackTrace(writer);
}
MatcherAssert.assertThat(
"error message is correct",
baos.toString(),
Matchers.containsString("the 'len' attribute must be a positive integer")
);
}

}
67 changes: 67 additions & 0 deletions eo-runtime/src/test/java/org/eolang/ExpectTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016-2024 Objectionary.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.eolang;

import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

/**
* Test for {@link Expect}.
*
* @since 1.0
*/
final class ExpectTest {

@Test
void buildsAndChecks() {
MatcherAssert.assertThat(
"passes through and throws correctly",
new Expect<>("something", () -> 42)
.must(i -> i > 0)
.otherwise("must be positive")
.that(i -> Integer.toString(i))
.must(s -> !s.isEmpty())
.it(),
Matchers.equalTo("42")
);
}

@Test
void failsWithCorrectTrace() {
MatcherAssert.assertThat(
"error message is correct",
Assertions.assertThrows(
ExFailure.class,
() -> new Expect<>("a number", () -> 42)
.must(i -> i < 0)
.otherwise("must be negative")
.it(),
"fails on check"
).getMessage(),
Matchers.containsString("negative")
);
}
}

0 comments on commit 1e6f5da

Please sign in to comment.