yicai.liu
yicai.liu

Reputation: 11

How can I modify the core api in java ?

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

Answers (2)

yicai.liu
yicai.liu

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

ndc85430
ndc85430

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:

  • The fine is 1 for every day the item is overdue
  • The fine is 0 if the item is not overdue

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

Related Questions