# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import enum
import unittest

from crossbench.helper.state import (BaseState, StateMachine,
                                     UnexpectedStateError)
from tests import test_helper


@enum.unique
class CustomState(BaseState):
  INITIAL = enum.auto()
  READY = enum.auto()
  DONE = enum.auto()


class StateMachineTestCase(unittest.TestCase):

  def test_init(self):
    state_machine = StateMachine(CustomState.INITIAL)
    self.assertIs(state_machine.state, CustomState.INITIAL)
    state_machine = StateMachine(CustomState.READY)
    self.assertIs(state_machine.state, CustomState.READY)

  def test_eq(self):
    state_machine = StateMachine(CustomState.READY)
    state_machine_2 = StateMachine(CustomState.READY)
    self.assertEqual(state_machine, state_machine)
    self.assertEqual(state_machine, state_machine_2)
    self.assertEqual(state_machine, CustomState.READY)
    self.assertNotEqual(state_machine, None)
    self.assertNotEqual(state_machine, CustomState.INITIAL)
    self.assertNotEqual(state_machine, StateMachine(CustomState.INITIAL))

  def test_transition(self):
    state_machine = StateMachine(CustomState.INITIAL)
    state_machine.transition(CustomState.INITIAL, to=CustomState.READY)
    self.assertEqual(state_machine.state, CustomState.READY)
    with self.assertRaises(UnexpectedStateError) as cm:
      state_machine.transition(CustomState.INITIAL, to=CustomState.READY)
    self.assertIn("INITIAL", str(cm.exception))
    self.assertIn("READY", str(cm.exception))

  def test_transition_multi_current(self):
    state_machine = StateMachine(CustomState.INITIAL)
    state_machine.transition(
        CustomState.INITIAL, CustomState.READY, to=CustomState.READY)
    self.assertEqual(state_machine.state, CustomState.READY)
    state_machine.transition(
        CustomState.INITIAL, CustomState.READY, to=CustomState.READY)
    self.assertEqual(state_machine.state, CustomState.READY)
    state_machine.transition(
        CustomState.INITIAL, CustomState.READY, to=CustomState.DONE)
    self.assertEqual(state_machine.state, CustomState.DONE)
    with self.assertRaises(UnexpectedStateError) as cm:
      state_machine.transition(
          CustomState.INITIAL, CustomState.READY, to=CustomState.DONE)
    self.assertIn("INITIAL", str(cm.exception))
    self.assertIn("READY", str(cm.exception))
    self.assertIn("DONE", str(cm.exception))

  def test_expect(self):
    state_machine = StateMachine(CustomState.INITIAL)
    state_machine.expect(CustomState.INITIAL)
    with self.assertRaises(RuntimeError) as cm:
      state_machine.expect(CustomState.READY)
    self.assertIn("INITIAL", str(cm.exception))
    self.assertIn("READY", str(cm.exception))

  def test_expect_before(self):
    state_machine = StateMachine(CustomState.INITIAL)
    state_machine.expect_before(CustomState.READY)
    state_machine.expect_before(CustomState.DONE)

    state_machine.transition(CustomState.INITIAL, to=CustomState.READY)
    with self.assertRaises(UnexpectedStateError) as cm:
      state_machine.expect_before(CustomState.READY)
    self.assertEqual(cm.exception.state, CustomState.READY)
    self.assertEqual(cm.exception.expected, (CustomState.INITIAL,))
    state_machine.expect_before(CustomState.DONE)

    state_machine.transition(CustomState.READY, to=CustomState.DONE)
    with self.assertRaises(UnexpectedStateError) as cm:
      state_machine.expect_before(CustomState.DONE)
    self.assertEqual(cm.exception.state, CustomState.DONE)
    self.assertEqual(cm.exception.expected,
                     (CustomState.INITIAL, CustomState.READY))

  def test_expect_at_least(self):
    state_machine = StateMachine(CustomState.INITIAL)
    with self.assertRaises(UnexpectedStateError) as cm:
      state_machine.expect_at_least(CustomState.READY)
    self.assertEqual(cm.exception.state, CustomState.INITIAL)
    self.assertEqual(cm.exception.expected,
                     (CustomState.READY, CustomState.DONE))
    with self.assertRaises(UnexpectedStateError) as cm:
      state_machine.expect_at_least(CustomState.DONE)
    self.assertEqual(cm.exception.state, CustomState.INITIAL)
    self.assertEqual(cm.exception.expected, (CustomState.DONE,))

    state_machine.transition(CustomState.INITIAL, to=CustomState.READY)
    state_machine.expect_at_least(CustomState.INITIAL)
    state_machine.expect_at_least(CustomState.READY)
    with self.assertRaises(UnexpectedStateError) as cm:
      state_machine.expect_at_least(CustomState.DONE)
    self.assertEqual(cm.exception.state, CustomState.READY)
    self.assertEqual(cm.exception.expected, (CustomState.DONE,))

    state_machine.transition(CustomState.READY, to=CustomState.DONE)
    state_machine.expect_at_least(CustomState.INITIAL)
    state_machine.expect_at_least(CustomState.READY)
    state_machine.expect_at_least(CustomState.DONE)


if __name__ == "__main__":
  test_helper.run_pytest(__file__)
