Reputation: 11
for example:I want to change LocalDateTime.now() return result in non-production enviroment, I am trying to invoke the following code,but error occors:
@Test
public void test3() {
System.out.println(LocalDateTime.now());
Instrumentation instrumentation = ByteBuddyAgent.install();
Class<?>[] cls = instrumentation.getAllLoadedClasses();
for (Class<?> cl : cls) {
if ("java.time.LocalDateTime".equals(cl.getName())) {
try {
LocalDateTime mockData = LocalDateTime.of(2020, 1, 1, 0, 0, 0, 0);
byte[] bytes = new ByteBuddy()
.redefine(LocalDateTime.class)
.method(ElementMatchers.named("now").and(ElementMatchers.takesNoArguments()))
.intercept(FixedValue.value(mockData))
.make()
.getBytes();
ClassDefinition classDefinition = new ClassDefinition(cl, bytes);
instrumentation.redefineClasses(classDefinition);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
System.out.println(LocalDateTime.now());
}
2024-03-03T15:33:59.291699 java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields) at java.instrument/sun.instrument.InstrumentationImpl.redefineClasses0(Native Method) at java.instrument/sun.instrument.InstrumentationImpl.redefineClasses(InstrumentationImpl.java:195) at com.yi.component.test.agent.bytecode.ByteBuddyTest.test3(ByteBuddyTest.java:76) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.ParentRunner.run(ParentRunner.java:413) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
I want to change LocalDateTime.now() return result in non-production enviroment
Upvotes: 0
Views: 144
Reputation: 11
Solution found :
solution 1:
git clone https://github.com/wolfcw/libfaketime.git
make
sudo make install
faketime '@2007-01-01 00:00:00' java -jar xxx.jar
solution 2:
Use linux 5.6+ kernel feature - time namespace
https://github.com/moby/moby/issues/39163
https://securitylabs.datadoghq.com/articles/container-security-fundamentals-part-2/#time-namespace
Upvotes: -1
Reputation: 1783
It sounds like what you actually want to do is be able to control the time in your tests, which is very sensible.
The simpler way to go about this is using dependency injection: instead of having the class you want to test know about the system clock, pass in the clock so that you can vary the clock you use for testing and for production.
As an example, let's say you need to compute fines for loaned items that are overdue (e.g. for a library or something). Your business rules may be:
Note that I'm using recent tools: JUnit 5 and records were introduced in Java 14 (I'm on 21).
Here is a definition of a loaned item:
import java.time.LocalDate;
public record LoanedItem(String description, LocalDate dueOn) {}
and test cases expressing the behaviour:
import org.junit.jupiter.api.Test;
import java.time.Clock;
import java.time.LocalDate;
import java.time.ZoneOffset;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class FineCalculatorTest {
@Test
public void the_fine_is_1_for_every_day_the_item_is_overdue() {
LocalDate dueOn = LocalDate.of(2024, 2, 1);
LoanedItem loanedItem = new LoanedItem("Lawnmower", dueOn);
int daysOverdue = 2;
LocalDate today = dueOn.plusDays(daysOverdue);
Clock clock = Clock.fixed(today.atStartOfDay().toInstant(ZoneOffset.UTC), ZoneOffset.UTC);
FineCalculator fineCalculator = new FineCalculator(clock);
long fine = fineCalculator.fineFor(loanedItem);
assertEquals(2, fine);
}
@Test
public void the_fine_is_0_if_the_item_is_not_overdue() {
LocalDate dueOn = LocalDate.of(2024, 2, 1);
LoanedItem loanedItem = new LoanedItem("Lawnmower", dueOn);
LocalDate today = dueOn.minusDays(7);
Clock clock = Clock.fixed(today.atStartOfDay().toInstant(ZoneOffset.UTC), ZoneOffset.UTC);
FineCalculator fineCalculator = new FineCalculator(clock);
long fine = fineCalculator.fineFor(loanedItem);
assertEquals(0, fine);
}
}
The key thing to note is that FineCalculator
takes, as a constructor parameter, an instance of java.time.Clock
.
Clock.fixed
creates a Clock
whose time is fixed at what you give it. In the first test, then, I create a clock that's fixed at two days after the item's due date:
int daysOverdue = 2;
LocalDate today = dueOn.plusDays(daysOverdue);
Clock clock = Clock.fixed(today.atStartOfDay().toInstant(ZoneOffset.UTC), ZoneOffset.UTC);
so we expect a fine of 2.
The second case is similar: I pass a clock that's fixed at 7 days before the due date, so we expect a fine of 0.
The implementation of FineCalculator
then uses the clock to work out what today is and calculate the fine:
import java.time.Clock;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
public class FineCalculator {
private Clock clock;
public FineCalculator(Clock clock) {
this.clock = clock;
}
public long fineFor(LoanedItem loanedItem) {
LocalDate today = clock.instant().atZone(ZoneOffset.UTC).toLocalDate();
long daysOverdue = ChronoUnit.DAYS.between(loanedItem.dueOn(), today);
if (daysOverdue < 0) {
daysOverdue = 0;
}
long fine = 1 * daysOverdue;
return fine;
}
}
When you create the FineCalculator
in the production code, you'd pass the system clock. Here's a sample main
:
import java.time.Clock;
import java.time.LocalDate;
import java.time.ZoneOffset;
public class Main {
public static void main(String[] args) {
LoanedItem item = new LoanedItem("Screwdriver", LocalDate.of(2024, 9, 1));
Clock clock = Clock.system(ZoneOffset.UTC);
FineCalculator fineCalculator = new FineCalculator(clock);
System.out.println("Loaned item: " + item);
System.out.println("Fine is: " + fineCalculator.fineFor(item));
}
}
Upvotes: 2