Repository: Lightning-AI/pytorch-lightning
File: tests/tests_pytorch/trainer/flags/test_val_check_interval.py (Python)

Code before:
        val_check_interval=100,
        logger=False,
        enable_checkpointing=False,
    )

    model = BoringModel()
    with pytest.raises(ValueError, match="must be less than or equal to the number of the training batches"):
        trainer.fit(model)


def test_val_check_interval_float_with_none_check_val_every_n_epoch():
    """Test that an exception is raised when `val_check_interval` is set to float with
    `check_val_every_n_epoch=None`"""
    with pytest.raises(
        MisconfigurationException,
        match=re.escape(
            "`val_check_interval` should be an integer or a time-based duration (str 'DD:HH:MM:SS', "
            "datetime.timedelta, or dict kwargs for timedelta) when `check_val_every_n_epoch=None`."
        ),
    ):
        Trainer(
            val_check_interval=0.5,
            check_val_every_n_epoch=None,
        )


def test_time_based_val_check_interval(tmp_path):
    call_count = {"count": 0}

    def fake_time():
        result = call_count["count"]
        call_count["count"] += 2
        return result

    with patch("time.monotonic", side_effect=fake_time):
        trainer = Trainer(
            default_root_dir=tmp_path,
            logger=False,
            enable_checkpointing=False,
            max_epochs=1,
            max_steps=5,  # 5 steps: simulate 10s total wall-clock time
            limit_val_batches=1,
            val_check_interval="00:00:00:02",  # every 2s
        )
        model = BoringModel()
        trainer.fit(model)

    # Assert 5 validations happened
    val_runs = trainer.fit_loop.epoch_loop.val_loop.batch_progress.total.completed
    # The number of validation runs should be equal to the number of times we called fake_time
    assert val_runs == 5, f"Expected 5 validations, got {val_runs}"

Review comment (suggestion):
it seems neither of these tests are checking that the time based validation works for all 3 input formats (string, dict, timedelta). Consider adding a test for `_parse_time_interval_seconds` or to parameterize one of the tests for the different formats

Code after:
        limit_train_batches=10,
        val_check_interval=100,
        logger=False,
        enable_checkpointing=False,
    )

    model = BoringModel()
    with pytest.raises(ValueError, match="must be less than or equal to the number of the training batches"):
        trainer.fit(model)


def test_val_check_interval_float_with_none_check_val_every_n_epoch():
    """Test that an exception is raised when `val_check_interval` is set to float with
    `check_val_every_n_epoch=None`"""
    with pytest.raises(
        MisconfigurationException,
        match=re.escape(
            "`val_check_interval` should be an integer or a time-based duration (str 'DD:HH:MM:SS', "
            "datetime.timedelta, or dict kwargs for timedelta) when `check_val_every_n_epoch=None`."
        ),
    ):
        Trainer(
            val_check_interval=0.5,
            check_val_every_n_epoch=None,
        )


@pytest.mark.parametrize(
    "interval",
    [
        "00:00:00:02",
        {"seconds": 2},
        timedelta(seconds=2),
    ],
)
def test_time_based_val_check_interval(tmp_path, interval):
    call_count = {"count": 0}

    def fake_time():
        result = call_count["count"]
        call_count["count"] += 2
        return result

    with patch("time.monotonic", side_effect=fake_time):
        trainer = Trainer(
            default_root_dir=tmp_path,
            logger=False,
            enable_checkpointing=False,
            max_epochs=1,
            max_steps=5,  # 5 steps: simulate 10s total wall-clock time
            limit_val_batches=1,