aplib-master/src/test/java/eu/iv4xr/framework/exampleTestAgentUsage/TestWithWrappingEnv_GCDGame.java
package eu.iv4xr.framework.exampleTestAgentUsage;

import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;

import static eu.iv4xr.framework.Iv4xrEDSL.*;
import static nl.uu.cs.aplib.AplibEDSL.*;

import eu.iv4xr.framework.exampleTestAgentUsage.Test_GCDGame.MyState;
import eu.iv4xr.framework.mainConcepts.*;
import nl.uu.cs.aplib.Logging;
import nl.uu.cs.aplib.agents.State;
import nl.uu.cs.aplib.environments.NullEnvironment;
import nl.uu.cs.aplib.mainConcepts.*;
import static eu.iv4xr.framework.mainConcepts.ObservationEvent.*;

/**
 * This class demonstrates how to use iv4xr
 * {@link eu.iv4xr.framework.mainConcepts.TestAgent} to test a simple class. The
 * class-under-test is the class {@link GCDGame}, which implement a simple game
 * over an imaginary 2D world where the player tries to get to a position (x,y)
 * which is relatively prime to each other (their greatest common divisor (gcd)
 * is 1).
 * 
 * <p>
 * The architecture of iv4xr agents insists that agents interact with the
 * "world" outside it through an interface called "environment" (an instance of
 * the class {@link nl.uu.cs.aplib.mainConcepts.Environment}, or its subclass).
 * This means, to test {@link GCDGame} we will also have to do it through an
 * instance of this Environment. More precisely, we will have to create our own
 * subclass of {@link nl.uu.cs.aplib.mainConcepts.Environment} which is
 * custom-made to facilitate interactions with {@link GCDGame}. This
 * architecture is meant primarily for testing systems which are outside the
 * agent's JVM. However for testing another Java class we can use a lighter
 * architecture. We show an example here.
 * 
 * <p>
 * In this example we will show an architecture that essentially bypasses the
 * Environment so that the agent directly access the APIs and the state of the
 * target class-under-test (CUT). We will still use an 'Environment', but only
 * as a wrapper to hold a reference to an instance of the CUT.
 *
 */
public class TestWithWrappingEnv_GCDGame {

    /**
     * Define an Environment to provide an interface between the test agent and the
     * program-under-test. Here, we will choose to simply wrap the environment over
     * the program-under-test.
     */
    static class GCDEnv extends NullEnvironment {
        /**
         * The instance of GCDGame that is to be tested, wrapped inside this
         * Environment.
         */
        GCDGame gcdgameUnderTest;

        GCDEnv(GCDGame gcdgame) {
            gcdgameUnderTest = gcdgame;
        }
        
        @Override
        public String toString() {
            return "(" + gcdgameUnderTest.x + "," + gcdgameUnderTest.y + "), gcd=" + gcdgameUnderTest.gcd + ", win="
                    + gcdgameUnderTest.win();
        }
    }

    /**
     * Define a new state-structure for the agent. For this example, we don't
     * actually need a new state-structure, but let's just pretend that we do.
     */
    static class MyState extends State {
        MyState() {
            super();
        }

        @Override
        public GCDEnv env() {
            return (GCDEnv) super.env();
        }
    }
    
    /**
	 * A constructor to construct "actions" (an instance of type 
	 * {@link Action}). They will be identified by their names:
	 * "up", "down", "left", "right", and "observe". When the action
	 * is invoked, the corresponding command will be sent to the GCDGame
	 * via the GCDEnv.
	 */
	Action myaction(String actionName) {
		return action("action " + actionName).do1((MyState S) -> {
			switch (actionName) {
			    case "up"      : S.env().gcdgameUnderTest.up(); break ;
			    case "down"    : S.env().gcdgameUnderTest.down(); break ;
			    case "left"    : S.env().gcdgameUnderTest.left(); break ;
			    case "right"   : S.env().gcdgameUnderTest.right(); break ;
			    case "observe" : break ;
			    default : throw new IllegalArgumentException() ;
			}
			Logging.getAPLIBlogger().info("new state: " + S);
			return S;
		});
	}

    // Construct a tactic to auto-drive the player to position X,Y:
    Tactic navigateTo(int X, int Y) {
        return FIRSTof(
        	myaction("up")   .on_((MyState S) -> S.env().gcdgameUnderTest.y < Y).lift(),
        	myaction("down") .on_((MyState S) -> S.env().gcdgameUnderTest.y > Y).lift(),
        	myaction("right").on_((MyState S) -> S.env().gcdgameUnderTest.x < X).lift(),
        	myaction("left") .on_((MyState S) -> S.env().gcdgameUnderTest.x > X).lift());
    }

    /**
     * A parameterized test-case to test GCDGame. Given X and Y, this specifies the
     * expected gcd value, and whether the GCDGame should conclude a win or lose. We
     * will use iv4xr test-agent to do the test.
     */
    public void parameterizedGCDGameTest(int X, int Y, int expectedGCD, boolean expectedWinConclusion) {

        // (1) Create a new GCDgame that is to be tested:
        var game = new GCDGame();
        Logging.getAPLIBlogger().info("STARTING a new test. Initial state: (" + game.x + ", " + game.y + ")");

        // (2) Create a fresh state + environment for the test agent:
        var state = new MyState() ;
        var env   = new GCDEnv(game) ;

        // (3) Create your test agent; attach the just created state and env to it:
        var agent = new TestAgent()
        		     . attachState(state)
                     . attachEnvironment(env) ;

        var info = "test gcd(" + X + "," + Y + ")";

        // (4) Define what is the testing task as a goal (to be solved by the agent):
        var topgoal = testgoal("tg")
                // the goal is to drive the game to get it to position (X,Y):
                .toSolve((MyState S) -> S.env().gcdgameUnderTest.x == X && S.env().gcdgameUnderTest.y == Y)
                // specify the tactic to solve the above goal:
                .withTactic(navigateTo(X, Y))
                // assert the correctness property that must hold on the state where the goal is
                // solved;
                // we will check that the gcd field and win() have correct values:
                // use .oracle(...)  or .invariant(...) both do the same 
                .oracle(agent,
                        (MyState S) -> assertTrue_("", info,
                                S.env().gcdgameUnderTest.gcd == expectedGCD
                                && S.env().gcdgameUnderTest.win() == expectedWinConclusion))
                // finally we lift the goal to become a GoalStructure, for technical reason.
                .lift();

        // (5) Attach the goal created above to your test-agent; well, and the
        // test-agent also need
        // a data-collector:
        var dataCollector = new TestDataCollector();
        agent. setTestDataCollector(dataCollector)
             . setGoal(topgoal);

        // (6) Ok, now we can run the agent to do the test:
        while (!topgoal.getStatus().success()) {
            agent.update();
        }

        // (7) And finally we verify that the agent didn't see anything wrong:
        assertTrue(dataCollector.getNumberOfFailVerdictsSeen() == 0);
        assertTrue(dataCollector.getNumberOfPassVerdictsSeen() == 1);
        Logging.getAPLIBlogger().info("TEST END.");
    }

    @Test
    /**
     * OK, let's now run a bunch of tests!
     */
    public void tests() {
        parameterizedGCDGameTest(0, 0, 0, false);
        parameterizedGCDGameTest(1, 1, 1, true);
        parameterizedGCDGameTest(12, 0, 12, false);
        parameterizedGCDGameTest(0, 9, 9, false);
        Logging.getAPLIBlogger().setUseParentHandlers(false);
        parameterizedGCDGameTest(32 * 7, 32 * 11, 32, false); // Test_GCD(7966496,314080416) --> takes too long :)
        parameterizedGCDGameTest(7, 11 * 11, 1, true);
        Logging.getAPLIBlogger().setUseParentHandlers(true);
    }

}
