import unittest
import shlex
from os import readlink, unlink, stat, major, minor
from os.path import exists, join
from subprocess import Popen, PIPE, STDOUT
from random import random
from time import sleep

# Non-exported device-mapper constants: used for tests only.
DM_NAME_LEN = 128  # includes NULL
DM_MAX_UUID_PREFIX_LEN = 15

# Format for dmpytestN test devices.
_uuid_prefix = "DMPY-"
_uuid_format = "%s%x"

_dev_mapper = "/dev/mapper"

_udev_wait_delay = 0.1

def _new_uuid():
    rand = random() * 2 ** 64
    return _uuid_format % (_uuid_prefix, int(rand))

def _get_cmd_output(cmd):
    """ Call `cmd` via `Popen` and return the status and combined `stdout`
        and `stderr` as a 2-tuple, e.g.:

        (0, "vg00/lvol0: Created new region with 1 area(s) as region ID 5\n")

    """
    args = shlex.split(cmd)

    p = Popen(args, shell=False, stdout=PIPE,
              stderr=STDOUT, bufsize=-1, close_fds=True)

    # stderr will always be None
    (stdout, stderr) = p.communicate()

    # Change the codec if testing in a non-utf8 environment.
    return (p.returncode, stdout.decode('utf-8'))

def _read_ahead_from_blockdev(dev_path):
    return int(_get_cmd_output("blockdev --getra %s" % dev_path)[1])

def _get_major_minor_from_stat(dev_path):
    st_buf = stat(dev_path)
    return (major(st_buf.st_rdev), minor(st_buf.st_rdev))

def _get_dm_major_from_dm_0_sysfs():
    dm0_sysfs_path = "/sys/block/dm-0/dev"
    major = None
    minor = None
    with open(dm0_sysfs_path, "r") as f:
        (major, minor) = f.read().strip().split(":")
    return int(major)

def _get_dm_major_from_proc():
    proc_devices_path = "/proc/devices"
    with open(proc_devices_path) as f:
        block_seen = False
        for line in f.readlines():
            if "Block" in line:
                block_seen = True
                continue
            if block_seen and "device-mapper" in line:
                (major, name) = line.split(" ")
                break
    return int(major)

# Try to find the (a) current device-mapper major number from sysfs,
# or /proc/devices. This is used to test dmpy.is_dm_major().
def _get_dm_major():
    try:
        return _get_dm_major_from_dm_0_sysfs()
    except:
        try:
            return _get_dm_major_from_proc()
        except:
            return 253

def _get_table_from_dmsetup(dm_name):
    return _get_cmd_output("dmsetup table %s" % dm_name)[1].strip()

def _get_driver_version_from_dmsetup():
    for line in _get_cmd_output("dmsetup version")[1].splitlines():
        if not line.startswith("Driver version"):
            continue
        return line.split(":")[1].lstrip()

def _create_stats(name, program_id="dmstats", nr_areas=1, precise=False,
                  start=None, length=None, aux_data=None):
    args = "--programid %s" % program_id
    if nr_areas > 1:
        args += " --areas %d" % nr_areas
    if precise:
        args += " --precise"
    if start:
        args += " --start %d" % start
    if length:
        args += " --length %d" % length
    if aux_data:
        args += " --userdata %s" % aux_data

    r = _get_cmd_output("dmstats create %s %s" % (args, name))
    if r[0]:
        raise OSError("Failed to create stats region")

def _remove_all_stats(name):
    # We use --allprograms --allregions here to work around dmstats versions
    # that are missing commit 5eda393:
	#
	#  commit 5eda3934885b23ce06f862a56b524ceaab3cb565
	#  Author: Bryn M. Reeves <bmr@redhat.com>
	#  Date:   Mon Oct 24 17:21:18 2016 +0100
	#
	#     dmsetup: obey --programid when deleting regions
	#
    # Without this fix attempting to delete our own regions with --programid
	# fails to remove all regions, causing assertion failures when dmpy tests
	# attempt to validate the expected number of regions.
    r = _get_cmd_output("dmstats delete --allprograms --allregions %s" % name)
    if r[0]:
        raise OSError("Failed to remove stats regions.")

def _create_loopback(path, size):
    loop_file = join(path, "dmpy-test-img0")
    r = _get_cmd_output("dd if=/dev/zero of=%s bs=%d count=1" %
                        (loop_file, size))
    if r[0]:
        raise OSError("Failed to create image file.")
    r = _get_cmd_output("losetup -f")
    if r[0]:
        raise OSError("Failed to find free loop device.")
    device = r[1].strip()
    r = _get_cmd_output("losetup %s %s" % (device, loop_file))
    return (device, loop_file)

def _remove_loopback(loop_device):
    device, loop_file = loop_device
    r = _get_cmd_output("losetup -d %s" % device)
    if r[0]:
        raise OSError("Failed to remove loop device.")
    unlink(loop_file)

def _create_linear_device(dev, size, uuid=_new_uuid):
    dm_name = "dmpytest0"
    sectors = size >> 9

    # if UUID is callable, call it.
    if hasattr(uuid, "__call__"):
        uuid = uuid()

    uuid_arg = ("--uuid %s" % uuid) if uuid else ""
    r = _get_cmd_output("dmsetup create %s %s "
                        "--table='0 %d ""linear %s 0'" %
                        (dm_name, uuid_arg, sectors, dev))
    if r[0]:
        raise OSError("Failed to create linear device.")

    return dm_name

def _remove_dm_device(dm_dev):
    r = _get_cmd_output("dmsetup remove %s" % dm_dev)
    if r[0]:
        raise OSError("Failed to remove dm device %s." % dm_dev)

class DmpyTests(unittest.TestCase):

    test_dev_size_bytes = 2**20
    test_dev_size_sectors = 2**11
    nodev = "quxquxquxquxqux"
    loop0 = None
    dmpytest0 = None
    program_id = "dmpytest"

    def udev_settle(self):
        _get_cmd_output("udevadm settle")

    def loop_minor(self, loop_name):
        return int(loop_name.split("loop")[1])

    def setUp(self):
        uuid = _new_uuid()
        dev_size = self.test_dev_size_bytes
        self.loop0 = _create_loopback("/var/tmp/", dev_size)
        self.dmpytest0_uuid = uuid
        self.dmpytest0 = _create_linear_device(self.loop0[0],
                                               dev_size, uuid=uuid)

    def tearDown(self):
        self.udev_settle()
        if (self.dmpytest0):
            _remove_dm_device(self.dmpytest0)
        if (self.loop0):
            _remove_loopback(self.loop0)

    def test_import(self):
        # attempt to import dmpy
        try:
            import dmpy as dm
        except Exception as e:
            self.fail(str(e))

    #
    # Dmpy module tests.
    #

    def test_dmpy_get_library_version(self):
        import dmpy as dm
        # Assert the expected major/minor version values (good since Nov 2005).
        libdm_major_minor = "1.02"
        self.assertTrue(dm.get_library_version().startswith(libdm_major_minor))

    def test_is_dm_major(self):
        import dmpy as dm
        # Assert that invalid dm major numbers return False.
        self.assertFalse(dm.is_dm_major(0))
        self.assertFalse(dm.is_dm_major(1))
        self.assertFalse(dm.is_dm_major(-1))
        # Assert that valid dm major numbers return True.
        self.assertTrue(dm.is_dm_major(_get_dm_major()))

    def test_update_nodes(self):
        pass  # FIXME: test with fake /dev and udev disabled.

    def test_set_get_name_mangling_mode(self):
        # Ensure that we get the same name_mangling_mode back as we set, and
        # that the default mode is as expected.
        import dmpy as dm
        # Assert that dm.STRING_MANGLING_AUTO is default.
        initial_mode = dm.get_name_mangling_mode()
        self.assertEqual(initial_mode, dm.STRING_MANGLING_AUTO)
        # Assert that we get each mangling mode back as expected.
        self.assertTrue(dm.set_name_mangling_mode(dm.STRING_MANGLING_NONE))
        self.assertEqual(dm.get_name_mangling_mode(), dm.STRING_MANGLING_NONE)
        self.assertTrue(dm.set_name_mangling_mode(dm.STRING_MANGLING_AUTO))
        self.assertEqual(dm.get_name_mangling_mode(), dm.STRING_MANGLING_AUTO)
        self.assertTrue(dm.set_name_mangling_mode(dm.STRING_MANGLING_HEX))
        self.assertEqual(dm.get_name_mangling_mode(), dm.STRING_MANGLING_HEX)

    def test_set_get_dev_dir(self):
        # Ensure that we get the same dev_dir back as we set, and
        # that the default directory is as expected.
        import dmpy as dm
        # Default device directory. If your libdevmapper was compiled with
        # a different value, change the definition of `default_dev_dir_get`.
        default_dev_dir_get = "/dev/mapper"
        other_dev_dir_get = "/var/tmp/mapper"
        other_dev_dir = "/var/tmp"
        reset_dev_dir = "/dev"
        # Assert that dm.STRING_MANGLING_AUTO is default.
        initial_dev_dir = dm.get_dev_dir()
        # Assert that the expected string is returned following a set.
        self.assertEqual(initial_dev_dir, default_dev_dir_get)
        self.assertTrue(dm.set_dev_dir(other_dev_dir))
        self.assertEqual(dm.get_dev_dir(), other_dev_dir_get)
        self.assertTrue(dm.set_dev_dir(reset_dev_dir))
        self.assertEqual(dm.get_dev_dir(), default_dev_dir_get)

    def test_set_get_sysfs_dir(self):
        # Ensure that we get the same dev_dir back as we set, and
        # that the default directory is as expected.
        import dmpy as dm
        # Default device directory. If your libdevmapper was compiled with
        # a different value, change the definition of `default_dev_dir_get`.
        default_sysfs_dir_get = "/sys/"
        # Assert that default_sysfs_dir_get is default.
        initial_sysfs_dir = dm.get_sysfs_dir()
        # Assert that the expected string is returned following a set.
        self.assertEqual(initial_sysfs_dir, default_sysfs_dir_get)

    def test_set_sysfs_dir_non_abs_path(self):
        # Assert that attampting to set a non-absolute sysfs path raises a
        # TypeError exception.
        import dmpy as dm
        with self.assertRaises(ValueError) as cm:
            dm.set_sysfs_dir("./tests/sys")

    def test_set_uuid_prefix_too_long(self):
        # Assert that dmpy.set_uuid_prefix() with a prefix length
        # > DM_MAX_UUID_PREFIX_LEN raises ValueError.
        import dmpy as dm
        with self.assertRaises(ValueError) as cm:
            dm.set_uuid_prefix("X" * (DM_MAX_UUID_PREFIX_LEN + 1))

    def test_set_get_uuid_prefix(self):
        # Assert that the expected prefix is returned following a set,
        # and that the default is `LVM-`.
        # FIXME: verify that the prefix is used in commands.
        import dmpy as dm
        default_uuid_prefix = "LVM-"
        new_uuid_prefix = "QUX-"
        self.assertEqual(dm.get_uuid_prefix(), default_uuid_prefix)
        self.assertTrue(dm.set_uuid_prefix(new_uuid_prefix))
        self.assertEqual(dm.get_uuid_prefix(), new_uuid_prefix)

    def test_lib_release_releases_fd(self):
        # Assert that a call to dmpy.lib_release() closes the ioctl file
        # descriptor.
        import dmpy as dm
        dev_mapper_control = join(_dev_mapper, "control")
        control_fd_path = "/proc/self/fd/3"

        # Run a DM_DEVICE_LIST to open the ioctl fd.
        dmt = dm.DmTask(dm.DM_DEVICE_LIST)
        dmt.run()
        dmt = None
        # Control fd path should exist now.
        self.assertTrue(exists(control_fd_path))
        self.assertEqual(dev_mapper_control, readlink(control_fd_path))
        # Control fd path should be close following lib_release().
        dm.lib_release()
        self.assertFalse(exists(control_fd_path))

    def test_hold_control_dev_open(self):
        # Assert that dmpy.hold_control_dev_open() returns True, that the
        # control device is held open across a subsequent call to
        # dm.lib_release(), and that it is closed after hold_control_dev is
        # disabled and a second call to dm_lib_release() made.
        import dmpy as dm
        dev_mapper_control = join(_dev_mapper, "control")
        control_fd_path = "/proc/self/fd/3"

        # Enable holding the control fd.
        dm.hold_control_dev(1)
        # Run a DM_DEVICE_LIST to open the ioctl fd.
        dmt = dm.DmTask(dm.DM_DEVICE_LIST)
        dmt.run()
        dmt = None
        # Control fd path should exist now.
        self.assertTrue(exists(control_fd_path))
        self.assertEqual(dev_mapper_control, readlink(control_fd_path))
        dm.lib_release()
        # Control fd path should exist now.
        self.assertTrue(exists(control_fd_path))
        dm.hold_control_dev(0)
        dm.lib_release()
        # Control fd path should not exist now.
        self.assertFalse(exists(control_fd_path))

    def test_mknodes(self):
        pass  # FIXME: test with fake /dev and udev disabled.

    def test_driver_version(self):
        # Assert that the driver version string returned by
        # `dmpy.driver_version()` matches the one returned by dmsetup.
        import dmpy as dm
        dmpy_drv_version = dm.driver_version()
        dmsetup_drv_version = _get_driver_version_from_dmsetup()
        self.assertEqual(dmpy_drv_version, dmsetup_drv_version)

    def test_dump_memory(self):
        # FIXME: test with custom logging fn?
        pass

    def test_set_get_udev_sync(self):
        # Assert that we get the expected result back after setting the
        # udev synchronization mode.
        import dmpy as dm

        # Change this if your libdevmapper was built with differnt defaults.
        initial_udev_sync = 1

        self.assertEqual(dm.udev_get_sync_support(), initial_udev_sync)
        # Try turning it off and on again.
        dm.udev_set_sync_support(0)
        self.assertEqual(dm.udev_get_sync_support(), 0)
        dm.udev_set_sync_support(1)
        self.assertEqual(dm.udev_get_sync_support(), 1)

    def test_set_get_udev_checks(self):
        # Assert that we get the expected result back after setting the
        # udev synchronization mode.
        import dmpy as dm

        # Change this if your libdevmapper was built with differnt defaults.
        initial_udev_check = 1

        self.assertEqual(dm.udev_get_checking(), initial_udev_check)
        # Try turning it off and on again.
        dm.udev_set_checking(0)
        self.assertEqual(dm.udev_get_checking(), 0)
        dm.udev_set_checking(1)
        self.assertEqual(dm.udev_get_checking(), 1)

    def test_cookie_supported(self):
        # Assert that the library returns the expected value of cookie_supported
        # depending on the library major/minor version values.
        import dmpy as dm
        (major, minor, patch) = map(int, dm.driver_version().split("."))
        if major >= 4 and minor >=15:
            self.assertTrue(dm.cookie_supported())
        else:
            self.assertFalse(dm.cookie_supported())

    def test_udev_create_cookie(self):
        # Assert that a new cookie, with non-zero value is created following a
        # call to dmpy.udev_create_cookie().
        import dmpy as dm
        cookie = dm.udev_create_cookie()
        self.assertTrue(cookie)
        self.assertTrue(cookie.value)
        self.assertTrue(cookie.udev_wait())

    def test_message_supports_precise_timestamps(self):
        # Assert that dm.message_supports_precise_timestamps() returns True.
        # FIXME: a better test would be to check the driver version from
        # dmsetup and assertTrue/assertFalse as appropriate. Today though,
        # it's unlikely that anyone running the suite is using a kernel old
        # enough to fail the test. Users of RHEL5, or older builds of RHEL6
        # and RHEL7 will fail this test.
        import dmpy as dm
        self.assertTrue(dm.message_supports_precise_timestamps())

    def test_stats_driver_supports_precise(self):
        # Assert that dm.stats_driver_supports_precise() returns True.
        # FIXME: see test_message_supports_precise_timestamps.
        import dmpy as dm
        self.assertTrue(dm.stats_driver_supports_precise())

    def test_stats_driver_supports_histogram(self):
        # Assert that dm.stats_driver_supports_histogram() returns True.
        # FIXME: see test_message_supports_precise_timestamps.
        import dmpy as dm
        self.assertTrue(dm.stats_driver_supports_histogram())

    def test_stats_all_programs(self):
        # Assert that the dmpy.STATS_ALL_PROGRAMS constant exists and has
        # the expected value.
        import dmpy as dm
        self.assertFalse(dm.STATS_ALL_PROGRAMS)
        self.assertEqual(dm.STATS_ALL_PROGRAMS, "")

    #
    # DmTask tests.
    #

    def test_dm_task_types_all_new(self):
        # test creation of each defined DM_DEVICE_* task type
        import dmpy as dm
        task_types = [
            dm.DM_DEVICE_CREATE,
            dm.DM_DEVICE_RELOAD,
            dm.DM_DEVICE_REMOVE,
            dm.DM_DEVICE_REMOVE_ALL,
            dm.DM_DEVICE_SUSPEND,
            dm.DM_DEVICE_RESUME,
            dm.DM_DEVICE_INFO,
            dm.DM_DEVICE_DEPS,
            dm.DM_DEVICE_RENAME,
            dm.DM_DEVICE_VERSION,
            dm.DM_DEVICE_STATUS,
            dm.DM_DEVICE_TABLE,
            dm.DM_DEVICE_WAITEVENT,
            dm.DM_DEVICE_LIST,
            dm.DM_DEVICE_CLEAR,
            dm.DM_DEVICE_MKNODES,
            dm.DM_DEVICE_LIST_VERSIONS,
            dm.DM_DEVICE_TARGET_MSG,
            dm.DM_DEVICE_SET_GEOMETRY
        ]
        for ttype in task_types:
            dmt = dm.DmTask(ttype)

    def test_dm_task_type_invalid_new(self):
        # test that creation of an invalid DmTask type fails.
        import dmpy as dm
        dmt = None
        with self.assertRaises(TypeError) as cm:
            dmt = dm.DmTask(2323)

    def _test_empty_task_method_raises(self, method):
        # Issue a DM_DEVICE_VERSION (which returns nothing but the driver
        # version), and then attempt to call `method`, which should be the
        # name of a `DmTask` method that raises `TypeError` when no data
        # is present.
        import dmpy as dm
        dmt = dm.DmTask(dm.DM_DEVICE_VERSION)
        dmt.run()
        runnable = getattr(dmt, method)
        self.assertTrue(runnable)
        self.assertRaises(TypeError, runnable)

    def test_empty_get_name_raises(self):
        # dm_task_get_name() returns the name from the results of an ioctl.
        # If no ioctl has been peformed (or if the ioctl did not return a
        # device name), then attempting to call dm_task_get_name() will
        # trigger a segmentation fault. The Python bindings need to detect
        # this and return the Py_None type.
        self._test_empty_task_method_raises("get_name")

    def test_empty_get_info_raises(self):
        # dm_task_get_info() returns the info from the results of an ioctl.
        # If no ioctl has been peformed (or if the ioctl did not return a
        # device info), then attempting to call dm_task_get_info() will
        # trigger a segmentation fault. The Python bindings need to detect
        # this and return the Py_None type.
        self._test_empty_task_method_raises("get_info")

    def test_empty_get_deps_raises(self):
        # dm_task_get_deps() returns the deps from the results of an ioctl.
        # If no ioctl has been peformed (or if the ioctl did not return
        # device deps), then attempting to call dm_task_get_deps() will
        # trigger a segmentation fault. The Python bindings need to detect
        # this and return the Py_None type.
        self._test_empty_task_method_raises("get_deps")

    def test_empty_get_uuid_raises(self):
        # dm_task_get_uuid() returns the uuid from the results of an ioctl.
        # If no ioctl has been peformed (or if the ioctl did not return
        # device uuid), then attempting to call dm_task_get_uuid() will
        # trigger a segmentation fault. The Python bindings need to detect
        # this and return the Py_None type.
        self._test_empty_task_method_raises("get_uuid")

    def test_empty_get_message_response_raises(self):
        # dm_task_get_message() returns the message from the results of an
        # ioctl. If no ioctl has been peformed (or if the ioctl did not return
        # device message), then attempting to call dm_task_get_message() will
        # trigger a segmentation fault. The Python bindings need to detect
        # this and return the Py_None type.
        self._test_empty_task_method_raises("get_message_response")

    def test_empty_get_names_raises(self):
        # dm_task_get_names() returns the names from the results of an ioctl.
        # If no ioctl has been peformed (or if the ioctl did not return
        # device names), then attempting to call dm_task_get_names() will
        # trigger a segmentation fault. The Python bindings need to detect
        # this and return the Py_None type.
        self._test_empty_task_method_raises("get_names")

    def test_get_name_list_and_check_types(self):
        # We don't really care what devices are present - just that we get
        # the expected list of 3-tuples with (str, int, int) types.
        #
        # Fail the test if no devices are found.
        import dmpy as dm
        dmt = dm.DmTask(dm.DM_DEVICE_LIST)
        dmt.run()
        names = dmt.get_names()
        self.assertTrue(len(names))
        for name in names:
            self.assertEqual(type(name[0]), str)  # name
            self.assertEqual(type(name[1]), int)  # major
            self.assertEqual(type(name[2]), int)  # minor

    def test_set_newname_name_ok(self):
        # Assert that a valid newname can be set via DmTask.set_newname()
        # on a DmTask(DM_DEVICE_RENAME) task.
        import dmpy as dm
        dmt = dm.DmTask(dm.DM_DEVICE_RENAME)
        dm_name_ok = (DM_NAME_LEN - 1) * "A"
        self.assertTrue(dmt.set_newname(dm_name_ok))

    def test_set_newname_null_name(self):
        # Assert that TypeError is raised when name is NULL.
        import dmpy as dm
        dmt = dm.DmTask(dm.DM_DEVICE_RENAME)
        dm_name_null = None
        with self.assertRaises(TypeError) as cm:
            dmt.set_newname(dm_name_null)

    def test_set_newname_empty_name(self):
        # Assert that ValueError is raised when name is "".
        import dmpy as dm
        dmt = dm.DmTask(dm.DM_DEVICE_RENAME)
        dm_name_empty = ""
        with self.assertRaises(ValueError) as cm:
            dmt.set_newname(dm_name_empty)

    def test_set_newname_name_has_slash(self):
        # Assert that ValueError is raised when name contains '/'
        import dmpy as dm
        dmt = dm.DmTask(dm.DM_DEVICE_RENAME)
        dm_name_has_slash = "/qux"
        with self.assertRaises(ValueError) as cm:
            dmt.set_newname(dm_name_has_slash)

    def test_set_newname_name_too_long(self):
        # Assert that ValueError is raised when len(name) > (DM_NAME_LEN - 1).
        import dmpy as dm
        dmt = dm.DmTask(dm.DM_DEVICE_RENAME)
        dm_name_too_long = DM_NAME_LEN * "A"
        with self.assertRaises(ValueError) as cm:
            dmt.set_newname(dm_name_too_long)

    def test_task_set_get_name(self):
        # Assert that `DmTask.set_name()` sets the dm name for a DM_DEVICE_INFO
        # ioctl, and returns the correct device information by checking
        # `DmTask.get_name()`.
        import dmpy as dm
        dmt = dm.DmTask(dm.DM_DEVICE_INFO)
        dmt.set_name(self.dmpytest0)
        dmt.run()
        self.assertEqual(self.dmpytest0, dmt.get_name())

    def test_task_get_info(self):
        # Assert that a non-NULL DmInfo object is returned following a
        # successful DM_DEVICE_INFO ioctl.
        import dmpy as dm
        dmt = dm.DmTask(dm.DM_DEVICE_INFO)
        dmt.set_name(self.dmpytest0)
        dmt.run()
        info = dmt.get_info()
        self.assertTrue(info)

    def test_task_info_fields_present(self):
        # Assert that the info.exists flag is non-zero for a valid device, and
        # that the dmpytest0 device has an active table, and is read-write.
        import dmpy as dm
        dmt = dm.DmTask(dm.DM_DEVICE_INFO)
        dmt.set_name(self.dmpytest0)
        dmt.run()
        info = dmt.get_info()
        self.assertTrue(info.exists)
        self.assertTrue(info.live_table)
        self.assertFalse(info.read_only)

    def test_task_info_fields_nodev(self):
        # Assert that the info.exists flag is zero for a non-existent device.
        import dmpy as dm
        dmt = dm.DmTask(dm.DM_DEVICE_INFO)
        dmt.set_name(self.nodev)
        dmt.run()
        info = dmt.get_info()
        self.assertFalse(info.exists)

    def test_get_uuid(self):
        # Get the device UUID with a DM_DEVICE_INFO ioctl, and assert that
        # the command succeeds, and that the returned UUID string matches
        # the stored value.
        import dmpy as dm
        dmt = dm.DmTask(dm.DM_DEVICE_INFO)
        dmt.set_name(self.dmpytest0)
        dmt.run()
        self.assertEqual(dmt.get_uuid(), self.dmpytest0_uuid)

    def test_get_deps(self):
        # Assert that a deps list is returned following a DM_DEVICE_DEPS
        # command, and that the major/minor number(s) of the dependencies
        # are as expected.
        import dmpy as dm
        # Stat the device for comparison
        (maj_stat, min_stat) = _get_major_minor_from_stat(self.loop0[0])
        dmt = dm.DmTask(dm.DM_DEVICE_DEPS)
        dmt.set_name(self.dmpytest0)
        dmt.run()
        deps = dmt.get_deps()
        # Expects: [(maj,min)]
        self.assertEqual(len(deps), 1)
        self.assertEqual(deps[0][0], maj_stat)
        self.assertEqual(deps[0][1], min_stat)

    def test_set_message_run_response(self):
        # Assert that setting a message succeeds, and that the ioctl runs
        # successfully and gives the expected response.
        import dmpy as dm
        dmt = dm.DmTask(dm.DM_DEVICE_TARGET_MSG)
        # Use a '@stats_create' as the message type - it will always succeed
        # on any target and system with stats support.
        dmt.set_name(self.dmpytest0)
        message = "@stats_create 0+%d /1" % self.test_dev_size_sectors
        self.assertTrue(dmt.set_message(message))
        dmt.run()
        region_id = dmt.get_message_response()
        self.assertTrue(int(region_id) >= 0)

    def test_set_newname_run_get_name(self):
        # Assert that setting a new name succeeds, and that the ioctl runs
        # successfully and returns the new name.
        import dmpy as dm
        newname = "dmpytest1"
        dmt = dm.DmTask(dm.DM_DEVICE_RENAME)
        dmt.set_name(self.dmpytest0)
        dmt.set_newname(newname)
        dmt.run()
        self.dmpytest0 = newname  # for tearDown()
        dmt = dm.DmTask(dm.DM_DEVICE_INFO)
        dmt.set_name(newname)
        dmt.run()
        self.assertEqual(dmt.get_name(), newname)

    def test_set_newuuid_with_no_uuid(self):
        # Assert that we can set a new UUID for a device that has none,
        # and that the new UUID is returned as expected.
        import dmpy as dm
        # We need a device with no UUID set.
        _remove_dm_device(self.dmpytest0)
        dmt = dm.DmTask(dm.DM_DEVICE_CREATE)
        dmpyuuidtest0 = "dmpyuuidtest0"
        self.dmpytest0 = dmpyuuidtest0  # for tearDown()
        dmt.set_name(dmpyuuidtest0)
        dmt.run()

        # Wait for udev to catch up
        self.udev_settle()

        # Generate a new UUID and apply it to the test device with a
        # DM_DEVICE_RENAME task.
        newuuid = _new_uuid()
        dmt = dm.DmTask(dm.DM_DEVICE_RENAME)
        dmt.set_name(self.dmpytest0)
        # Assert that the new UUID is set.
        self.assertTrue(dmt.set_newuuid(newuuid))
        dmt.run()

        # Get a DM_DEVICE_INFO of the device to compare.
        dmt = dm.DmTask(dm.DM_DEVICE_INFO)
        dmt.set_name(self.dmpytest0)
        dmt.run()

        # Assert that the retrieved UUID matches.
        self.assertEqual(dmt.get_uuid(), newuuid)

    def test_newuuid_with_uuid_set_fails(self):
        # Assert that attempting to set a UUID on an active device that
        # already has one set raises an exception.
        import dmpy as dm

        # Generate a new UUID and apply it to the test device with a
        # DM_DEVICE_RENAME task.
        newuuid = _new_uuid()
        dmt = dm.DmTask(dm.DM_DEVICE_RENAME)
        dmt.set_name(self.dmpytest0)

        # Assert that the new UUID is set: this should succeed, and the
        # subsequent dmt.run() should raise an exception based on the
        # errno set by the task ioctl.
        self.assertTrue(dmt.set_newuuid(newuuid))

        with self.assertRaises(OSError) as cm:
            dmt.run()

        self.assertEqual(dmt.get_errno(), 22)  # EINVAL

    def test_task_get_driver_version(self):
        # Assert that we can obtain the driver version from a task, and
        # that the result matches that obtained from dmsetup.
        import dmpy as dm
        dmt = dm.DmTask(dm.DM_DEVICE_INFO)
        dmt.set_name(self.dmpytest0)
        dmt.run()
        _dmsetup_driver_version = _get_driver_version_from_dmsetup()
        _driver_version = dmt.get_driver_version()
        self.assertTrue(_driver_version)
        self.assertEqual(_dmsetup_driver_version, _driver_version)

    def test_task_set_major_and_set_minor(self):
        # Send a DM_DEVICE_INFO task by major and minor number, and assert
        # that the expected device name is returned.
        import dmpy as dm
        dev_path = join(_dev_mapper, self.dmpytest0)
        (major, minor) = _get_major_minor_from_stat(dev_path)
        dmt = dm.DmTask(dm.DM_DEVICE_INFO)
        dmt.set_major(major)
        dmt.set_minor(minor)
        dmt.run()
        self.assertEqual(dmt.get_name(), self.dmpytest0)

    def test_task_set_major_minor(self):
        # Send a DM_DEVICE_INFO task by major and minor number, and assert
        # that the expected device name is returned.
        import dmpy as dm
        dev_path = join(_dev_mapper, self.dmpytest0)
        (major, minor) = _get_major_minor_from_stat(dev_path)
        dmt = dm.DmTask(dm.DM_DEVICE_INFO)
        dmt.set_major_minor(major, minor)
        dmt.run()
        self.assertEqual(dmt.get_name(), self.dmpytest0)

    def test_task_set_uid(self):
        # Setting UIDs should be handled by udev rules now.
        pass

    def test_task_set_gid(self):
        # Setting GIDs should be handled by udev rules now.
        pass

    def test_task_set_mode(self):
        # Setting device node modes should be handled by udev rules now.
        pass

    def test_task_get_errno(self):
        # Run a DM_DEVICE_INFO ioctl for a non-existent device, and assert
        # that the errno returned by DmTask.get_errno() is 6 (ENXIO).
        import dmpy as dm
        dmt = dm.DmTask(dm.DM_DEVICE_INFO)
        dmt.set_name(self.nodev)
        dmt.run()
        self.assertEqual(dmt.get_errno(), 6)

    def test_task_set_sector(self):
        # Assert that setting a message succeeds, and that the ioctl runs
        # successfully and gives the expected response.
        import dmpy as dm
        dmt = dm.DmTask(dm.DM_DEVICE_TARGET_MSG)
        dmt.set_name(self.dmpytest0)
        dmt.set_message("@stats_list")
        self.assertTrue(dmt.set_sector(0))
        dmt.run()
        response = dmt.get_message_response()
        self.assertEqual(response, "")

    def test_task_no_flush(self):
        # Assert that setting noflush on a DM_DEVICE_SUSPEND task succeeds.
        # FIXME: no testing of the flag's behaviour is done.
        import dmpy as dm
        dmt = dm.DmTask(dm.DM_DEVICE_SUSPEND)
        dmt.set_name(self.dmpytest0)
        self.assertTrue(dmt.no_flush())
        dmt.run()

    def test_task_no_open_count(self):
        # Assert that setting no_open_count on a DM_DEVICE_INFO task succeeds,
        # and that the resulting task open count is zero.
        import dmpy as dm
        dmt = dm.DmTask(dm.DM_DEVICE_INFO)
        dmt.set_name(self.dmpytest0)
        self.assertTrue(dmt.no_open_count())
        dmt.run()
        info = dmt.get_info()
        # NOTE: with kernel-4.7 and device-mapper 1.02.122 the open count is
        # returned regardless of the setting of no_open_count: the following
        # assertion is true simply because the test device is not open. If
        # the block is enclosed in a 'with' clause that opens the device, it
        # will fail.
        #
        # The code that implemented "no_open_count" was removed from the
        # kernel in 2006:
        #
        #  commit 5c6bd75d06db512515a3781aa97e42df2faf0815
        #  Author: Alasdair G Kergon <agk@redhat.com>
        #  Date:   Mon Jun 26 00:27:34 2006 -0700
        #
        #  [PATCH] dm: prevent removal if open
        #
        # And the DM_SKIP_BDGET_FLAG that implements it was marked as
        # ignored:
        #
        # /*
        #  * This flag is now ignored.
        #  */
        # #define DM_SKIP_BDGET_FLAG      (1 << 9) /* In */
        #
        # self.assertFalse(info.open_count)

    def test_task_set_geometry(self):
        # Assert that setting the geometry strings succeeds,
        import dmpy as dm
        geometry = ("62260", "255", "63", "64")
        dmt = dm.DmTask(dm.DM_DEVICE_SET_GEOMETRY)
        self.assertTrue(dmt.set_geometry(*geometry))
        dmt.set_name(self.dmpytest0)
        # FIXME: kernel bug?
        # Trying to run this task, with kernel 4.7 and device-mapper 1.02.122
        # fails with EINVAL, and an error in dmesg:
        #
        #   ioctl: Unable to interpret geometry settings.
        #
        # Indicating the wrong number of string arguments received by
        # drivers/md/dm-ioctl.c:dev_set_geometry().
        #dmt.run()

    def test_task_create_single_linear_target_udev(self):
        # Attempt to create a simple device with a single, linear target,
        # and assert that the device node exists, and that the device
        # table (as reported by dmsetup) matches the expected table.
        import dmpy as dm
        # Use a new device name - we will create and destroy it during
        # the test.
        dmpytest1 = "dmpytest1"
        loop_minor = self.loop_minor(self.loop0[0])
        expected_table = "0 2048 linear 7:%d 0" % loop_minor
        dmt = dm.DmTask(dm.DM_DEVICE_CREATE)
        dmt.set_name(dmpytest1)
        cookie = dm.DmCookie()
        self.assertTrue(dmt.set_cookie(cookie))

        self.assertTrue(dmt.add_target(0, self.test_dev_size_sectors,
                                       "linear", "%s 0" % self.loop0[0]))
        dmt.set_uuid(_new_uuid())
        dmt.run()

        # Wait for udev to complete
        self.assertTrue(cookie.udev_wait())

        # Assert that the device node exists
        self.assertTrue(exists(join(_dev_mapper, dmpytest1)))

        table = _get_table_from_dmsetup(dmpytest1)
        self.assertEqual(table, expected_table)

        # Remove the device and its node
        dmt = dm.DmTask(dm.DM_DEVICE_REMOVE)
        dmt.set_name(dmpytest1)
        cookie = dm.DmCookie()
        self.assertTrue(dmt.set_cookie(cookie))
        dmt.run()

        # Wait for udev to complete
        self.assertTrue(cookie.udev_wait())

        # Assert that the device node no longer exists
        self.assertFalse(exists(join(_dev_mapper, dmpytest1)))

    def test_task_create_single_linear_target_no_udev(self):
        # Attempt to create a simple device with a single, linear target,
        # and assert that the device node exists, and that the device
        # table (as reported by dmsetup) matches the expected table.
        import dmpy as dm
        # Use a new device name - we will create and destroy it during
        # the test.
        dmpytest1 = "dmpytest1"
        loop_minor = self.loop_minor(self.loop0[0])
        expected_table = "0 2048 linear 7:%d 0" % loop_minor
        dm.udev_set_sync_support(0)
        dmt = dm.DmTask(dm.DM_DEVICE_CREATE)
        dmt.set_name(dmpytest1)
        self.assertTrue(dmt.add_target(0, self.test_dev_size_sectors,
                                       "linear", "%s 0" % self.loop0[0]))
        dmt.set_uuid(_new_uuid())
        dmt.run()

        # update device nodes
        dm.update_nodes()

        # Assert that the device node exists
        self.assertTrue(exists(join(_dev_mapper, dmpytest1)))

        table = _get_table_from_dmsetup(dmpytest1)
        self.assertEqual(table, expected_table)

        # Remove the device and its node
        dmt = dm.DmTask(dm.DM_DEVICE_REMOVE)
        dmt.set_name(dmpytest1)
        dmt.run()

        # update device nodes
        dm.update_nodes()

        # Assert that the device node no longer exists
        self.assertFalse(exists(join(_dev_mapper, dmpytest1)))
        dm.udev_set_sync_support(1)

    #
    # Cookie tests
    #

    def test_new_cookie_not_ready(self):
        # Create a new cookie, assert that it is not ready, and then
        # destroy it.
        import dmpy as dm
        cookie = dm.udev_create_cookie()
        self.assertFalse(cookie.ready)
        cookie.udev_wait()

    def test_cookie_ready_after_wait(self):
        # Create a new cookie, wait on it, and assert that it becomes
        # ready following the call to cookie.udev_wait().
        import dmpy as dm
        cookie = dm.udev_create_cookie()
        self.assertFalse(cookie.ready)
        self.assertTrue(cookie.udev_wait())
        self.assertTrue(cookie.ready)

    def test_cookie_multiple_wait(self):
        # Create a new cookie, wait on it, and assert that a further attempt
        # to call udev_wait() raises a ValueError exception.
        import dmpy as dm
        cookie = dm.udev_create_cookie()
        self.assertFalse(cookie.ready)
        self.assertTrue(cookie.udev_wait())
        self.assertTrue(cookie.ready)
        with self.assertRaises(ValueError) as cm:
            cookie.udev_wait()

    def test_cookie_wait_immediate(self):
        # Create a new cookie, wait on it, and assert that it becomes ready.
        import dmpy as dm
        _remove_dm_device(self.dmpytest0)
        dmt = dm.DmTask(dm.DM_DEVICE_CREATE)
        dmt.set_name(self.dmpytest0)
        cookie = dm.udev_create_cookie()
        self.assertFalse(cookie.ready)
        dmt.set_cookie(cookie)
        dmt.add_target(0, self.test_dev_size_sectors,
                       "linear", "%s 0" % self.loop0[0])
        dmt.run()

        cookie.udev_wait(immediate=True)
        while not cookie.ready:
            cookie.udev_wait(immediate=True)
            sleep(_udev_wait_delay)

        self.assertTrue(cookie.ready)

    #
    # DmStats tests
    #

    def test_stats_create_program_id(self):
        # Assert that creating a DmStats handle with program_id=None
        # returns a valid object.
        import dmpy as dm
        dms = dm.DmStats(self.program_id)
        self.assertTrue(dms.__init__)
        self.assertEqual(type(dms), dm.DmStats)

    def test_stats_create_all_programs(self):
        # Assert that creating a DmStats handle with dm.STATS_ALL_PROGRAMS
        # returns a valid object.
        import dmpy as dm
        dms = dm.DmStats(dm.STATS_ALL_PROGRAMS)
        self.assertTrue(dms.__init__)
        self.assertEqual(type(dms), dm.DmStats)

    def test_stats_create_no_program_id(self):
        # Assert that creating a DmStats handle with program_id=None
        # returns a valid object.
        import dmpy as dm
        dms = dm.DmStats(None)
        self.assertTrue(dms.__init__)
        self.assertEqual(type(dms), dm.DmStats)

    def test_stats_create_bind_name(self):
        # Assert that creating a DmStats handle and binding it to a name via
        # the name= keword argument returns a valid object.
        import dmpy as dm
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        self.assertTrue(dms.__init__)
        self.assertEqual(type(dms), dm.DmStats)

    def test_stats_create_no_program_id_bind_name(self):
        # Assert that creating a DmStats handle with program_id=None
        # and a name= keyword argument returns a valid object.
        import dmpy as dm
        dms = dm.DmStats(None, name=self.dmpytest0)
        self.assertTrue(dms.__init__)
        self.assertEqual(type(dms), dm.DmStats)

    def test_stats_create_multiple_bind_raises(self):
        # Assert that attempting to pass multiple device binding kwargs
        # raises a TypeError exception.
        import dmpy as dm
        with self.assertRaises(TypeError) as cm:
            dm.DmStats(self.program_id, name="foo", uuid="qux")

    def test_stats_bind_name_none(self):
        # Assert that attempting to bind a NULL name raises TypeError.
        import dmpy as dm
        dms = dm.DmStats(self.program_id)
        with self.assertRaises(TypeError) as cm:
            dms.bind_name(None)

    def test_stats_bind_name_empty(self):
        # Assert that attempting to bind an empty name raises ValueError.
        import dmpy as dm
        dms = dm.DmStats(self.program_id)
        with self.assertRaises(ValueError) as cm:
            dms.bind_name("")

    def test_stats_bind_name_valid(self):
        # Assert that attempting to bind a valid name succeeds.
        import dmpy as dm
        dms = dm.DmStats(self.program_id)
        self.assertTrue(dms.bind_name(self.dmpytest0))

    def test_stats_bind_uuid_none(self):
        # Assert that attempting to bind a NULL uuid raises TypeError.
        import dmpy as dm
        dms = dm.DmStats(self.program_id)
        with self.assertRaises(TypeError) as cm:
            dms.bind_uuid(None)

    def test_stats_bind_uuid_empty(self):
        # Assert that attempting to bind an empty uuid raises ValueError.
        import dmpy as dm
        dms = dm.DmStats(self.program_id)
        with self.assertRaises(ValueError) as cm:
            dms.bind_uuid("")

    def test_stats_bind_uuid_valid(self):
        # Assert that attempting to bind a valid uuid succeeds.
        import dmpy as dm
        dms = dm.DmStats(self.program_id)
        self.assertTrue(dms.bind_uuid(_new_uuid()))

    def test_stats_bind_devno_valid(self):
        # Assert that attempting to bind a valid devno succeeds.
        import dmpy as dm
        dms = dm.DmStats(self.program_id)
        # FIXME: generate a valid DM major/minor pair.
        self.assertTrue(dms.bind_devno(253, 0))

    def test_stats_new_has_no_regions(self):
        # Assert that a newly created / bound stats handle returns zero
        # regions.
        import dmpy as dm
        dms = dm.DmStats(dm.STATS_ALL_PROGRAMS)
        self.assertTrue(dms.__init__)
        self.assertFalse(dms.nr_regions())

    def test_stats_new_has_no_groups(self):
        # Assert that a newly created / bound stats handle returns zero
        # groups.
        import dmpy as dm
        dms = dm.DmStats(dm.STATS_ALL_PROGRAMS)
        self.assertTrue(dms.__init__)
        self.assertFalse(dms.nr_groups())

    def test_stats_new_has_no_areas(self):
        # Assert that a newly created / bound stats handle returns zero
        # areas.
        import dmpy as dm
        dms = dm.DmStats(dm.STATS_ALL_PROGRAMS)
        self.assertTrue(dms.__init__)
        self.assertFalse(dms.nr_areas())

    def test_stats_not_present_region_is_not_present(self):
        # Assert that a non-existent region_id returns False when passed
        # to DmStats.region_present().
        import dmpy as dm
        dms = dm.DmStats(dm.STATS_ALL_PROGRAMS)
        self.assertFalse(dms.region_present(-1))

    def test_stats_new_handle_region_has_no_areas(self):
        # Assert that a newly created / bound stats handle returns zero
        # areas.
        import dmpy as dm
        dms = dm.DmStats(dm.STATS_ALL_PROGRAMS)
        self.assertTrue(dms.__init__)
        self.assertFalse(dms.region_nr_areas(0))

    def test_stats_new_handle_no_group_present(self):
        # Assert that groups are not present in a newly created DmStats object.
        import dmpy as dm
        dms = dm.DmStats(dm.STATS_ALL_PROGRAMS)
        self.assertFalse(dms.group_present(0))
        self.assertFalse(dms.group_present(1))
        self.assertFalse(dms.group_present(-1))

    def test_stats_set_get_sampling_interval(self):
        # Assert that setting a sampling interval is raises no error, and that
        # the set value is returned by a subsequent get.
        import dmpy as dm
        dms = dm.DmStats(dm.STATS_ALL_PROGRAMS)
        self.assertTrue(dms.set_sampling_interval(0.5))
        self.assertEqual(dms.get_sampling_interval(), 0.5)

    def test_stats_set_none_program_id_no_allow_empty_raises(self):
        # Assert that attempting to set None as the program_id fails if the
        # allow_empty flag is not given.
        import dmpy as dm
        dms = dm.DmStats(self.program_id)
        with self.assertRaises(ValueError) as cm:
            dms.set_program_id(None)
        with self.assertRaises(ValueError) as cm:
            dms.set_program_id("")

    def test_stats_set_none_program_id_with_allow_empty(self):
        # Assert that attempting to set None as the program_id fails if the
        # allow_empty flag is not given.
        import dmpy as dm
        dms = dm.DmStats(self.program_id)
        dms.set_program_id(None, allow_empty=True)
        dms.set_program_id("", allow_empty=True)

    def test_stats_set_program_id(self):
        # Assert that setting a valid program_id succeeds.
        import dmpy as dm
        dms = dm.DmStats(self.program_id)
        self.assertTrue(dms.set_program_id("qux"))

    def test_stats_list(self):
        # Assert that listing an empty device yields an empty
        # DmStats object, and that the correct number of regions is
        # returned when listing a device with regions present.
        import dmpy as dm
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        self.assertFalse(dms.list())
        self.assertEqual(len(dms), 0)
        _create_stats(self.dmpytest0, nr_areas=1, program_id=self.program_id)
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        self.assertTrue(dms.list())
        self.assertEqual(len(dms), 1)
        _remove_all_stats(self.dmpytest0)
        _create_stats(self.dmpytest0, nr_areas=4, program_id=self.program_id)
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        self.assertTrue(dms.list())
        self.assertEqual(len(dms), 1)

    def test_stats_populate_empty(self):
        # Assert that populating an empty device yields an empty
        # DmStats object, and that the correct number of regions is
        # returned when populating a device with regions present.
        import dmpy as dm
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        with self.assertRaises(OSError) as cm:
            dms.populate()
        self.assertEqual(len(dms), 0)

    def test_stats_populate_unlisted_raises(self):
        import dmpy as dm
        _create_stats(self.dmpytest0, nr_areas=1, program_id=self.program_id)
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        # Attempting to populate a single region in an empty dm_stats handle
        # is an error (since the region tables have not been dimensioned):
        # a single-region populate() without a prior list() should raise an
        # OSError exception.
        with self.assertRaises(OSError) as cm:
            dms.populate(region_id=0)
        self.assertEqual(len(dms), 0)

    def test_stats_populate_bogus_raises(self):
        import dmpy as dm
        _create_stats(self.dmpytest0, nr_areas=1, program_id=self.program_id)
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        self.assertTrue(dms.list())
        with self.assertRaises(OSError) as cm:
            dms.populate(region_id=1)  # dms has only region_id=0
        self.assertEqual(len(dms), 0)

    def test_stats_populate_one_region(self):
        import dmpy as dm
        _create_stats(self.dmpytest0, nr_areas=1, program_id=self.program_id)
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        self.assertTrue(dms.list())
        self.assertTrue(dms.populate(region_id=0))
        self.assertEqual(len(dms), 1)

    def test_stats_populate_all_regions(self):
        import dmpy as dm
        _create_stats(self.dmpytest0, nr_areas=1, program_id=self.program_id)
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        self.assertTrue(dms.populate())
        self.assertEqual(len(dms), 1)

    #
    # DmStatsRegion tests.
    #

    def test_region_nr_areas(self):
        # Assert that listing an empty device yields an empty
        # DmStats object, and that the correct number of regions is
        # returned when listing a device with regions present.
        import dmpy as dm
        _create_stats(self.dmpytest0, nr_areas=1, program_id=self.program_id)
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        self.assertTrue(dms.list())
        self.assertEqual(len(dms), 1)
        self.assertEqual(dms[0].nr_areas, 1)
        _remove_all_stats(self.dmpytest0)
        _create_stats(self.dmpytest0, nr_areas=4, program_id=self.program_id)
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        self.assertTrue(dms.list())
        self.assertEqual(len(dms), 1)
        self.assertEqual(dms[0].nr_areas, 4)

    def test_valid_region_nr_areas_seq_check_noraise(self):
        # Assert that when an attempt is made to access a member of a
        # DmStatsRegion, that depends on the parent DmStats' state, *after*
        # an invalidating DmStats operation, correctly causes the LookupError
        # exception to be raised.
        import dmpy as dm
        _create_stats(self.dmpytest0, nr_areas=1, program_id=self.program_id)
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        dms.populate()
        self.assertTrue(len(dms[0]))
        # Take a reference on a DmStatsRegion
        region = dms[0]
        # Access the parent via reference.
        self.assertTrue(region.nr_areas)

    def test_invalid_region_nr_areas_seq_check_raises(self):
        # Assert that when an attempt is made to access a member of a
        # DmStatsRegion, that depends on the parent DmStats' state, *after*
        # an invalidating DmStats operation, correctly causes the LookupError
        # exception to be raised.
        import dmpy as dm
        _create_stats(self.dmpytest0, nr_areas=1, program_id=self.program_id)
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        dms.populate()
        self.assertTrue(len(dms[0]))
        # Take a reference on a DmStatsRegion
        region = dms[0]
        # Invalidate the whole DmStats
        dms.list()
        with self.assertRaises(LookupError) as cm:
            region.nr_areas

    def test_dmstatsregion_precise_attr(self):
        # Assert that region precise_timestamps attributes have the expected
        # value following a list() or populate() operation.
        import dmpy as dm
        _create_stats(self.dmpytest0, program_id=self.program_id)
        _create_stats(self.dmpytest0, precise=True, program_id=self.program_id)
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        dms.list()
        self.assertFalse(dms[0].precise_timestamps)
        self.assertTrue(dms[1].precise_timestamps)
        dms.populate()
        self.assertFalse(dms[0].precise_timestamps)
        self.assertTrue(dms[1].precise_timestamps)

    def test_dmstatsregion_startlen_attr(self):
        # Assert that the start, and length attributes returned by a
        # DmStatsRegion object match those used to create it with dmstats.
        import dmpy as dm
        _create_stats(self.dmpytest0, program_id=self.program_id)
        _create_stats(self.dmpytest0, program_id=self.program_id, start=1024,
                      length=512)
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        dms.list()
        self.assertEqual(dms[0].start, 0)
        self.assertEqual(dms[0].len, 2048)
        self.assertEqual(dms[1].start, 1024)
        self.assertEqual(dms[1].len, 512)
        dms.populate()
        self.assertEqual(dms[0].start, 0)
        self.assertEqual(dms[0].len, 2048)
        self.assertEqual(dms[1].start, 1024)
        self.assertEqual(dms[1].len, 512)

    def test_dmstatsregion_arealen_attr(self):
        import dmpy as dm
        _create_stats(self.dmpytest0, program_id=self.program_id)
        _create_stats(self.dmpytest0, program_id=self.program_id, nr_areas=8)
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        dms.list()
        self.assertEqual(dms[0].area_len, 2048)
        self.assertEqual(dms[1].area_len, 256)
        dms.populate()
        self.assertEqual(dms[0].area_len, 2048)
        self.assertEqual(dms[1].area_len, 256)

    def test_dmstatsregion_program_id_attr(self):
        import dmpy as dm
        _create_stats(self.dmpytest0, program_id="qux")
        _create_stats(self.dmpytest0, program_id=self.program_id)
        dms = dm.DmStats(dm.STATS_ALL_PROGRAMS, name=self.dmpytest0)
        dms.list(dm.STATS_ALL_PROGRAMS)
        self.assertEqual(dms[0].program_id, "qux")
        self.assertEqual(dms[1].program_id, self.program_id)
        dms.populate(program_id=dm.STATS_ALL_PROGRAMS)
        self.assertEqual(dms[0].program_id, "qux")
        self.assertEqual(dms[1].program_id, self.program_id)

    def test_dmstatsregion_aux_data_attr(self):
        import dmpy as dm
        _create_stats(self.dmpytest0, aux_data="quxfoo")
        dms = dm.DmStats(dm.STATS_ALL_PROGRAMS, name=self.dmpytest0)
        dms.list(dm.STATS_ALL_PROGRAMS)
        self.assertEqual(dms[0].aux_data, "quxfoo")
        dms.populate(program_id=dm.STATS_ALL_PROGRAMS)
        self.assertEqual(dms[0].aux_data, "quxfoo")

    def test_dmstatsarea_startofflen_attr(self):
        import dmpy as dm
        _create_stats(self.dmpytest0, program_id=self.program_id)
        _create_stats(self.dmpytest0, program_id=self.program_id, nr_areas=8)
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        dms.list()
        self.assertEqual(dms[0][0].start, 0)
        self.assertEqual(dms[0][0].len, 2048)
        self.assertEqual(dms[0][0].offset, 0)
        self.assertEqual(dms[1][0].start, 0)
        self.assertEqual(dms[1][0].len, 256)
        self.assertEqual(dms[1][0].offset, 0)
        self.assertEqual(dms[1][1].start, 256)
        self.assertEqual(dms[1][1].len, 256)
        self.assertEqual(dms[1][1].offset, 256)
        dms.populate()
        self.assertEqual(dms[0][0].start, 0)
        self.assertEqual(dms[0][0].len, 2048)
        self.assertEqual(dms[0][0].offset, 0)
        self.assertEqual(dms[1][0].start, 0)
        self.assertEqual(dms[1][0].len, 256)
        self.assertEqual(dms[1][0].offset, 0)
        self.assertEqual(dms[1][1].start, 256)
        self.assertEqual(dms[1][1].len, 256)
        self.assertEqual(dms[1][1].offset, 256)

    def test_dmstatsarea_region_id_attr(self):
        import dmpy as dm
        _create_stats(self.dmpytest0, program_id=self.program_id)
        _create_stats(self.dmpytest0, program_id=self.program_id, nr_areas=8)
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        dms.list()
        self.assertEqual(dms[0].region_id, 0)
        self.assertEqual(dms[1].region_id, 1)
        dms.populate()
        self.assertEqual(dms[0].region_id, 0)
        self.assertEqual(dms[1].region_id, 1)

    def test_dmstatsarea_area_id_attr(self):
        import dmpy as dm
        _create_stats(self.dmpytest0, program_id=self.program_id)
        _create_stats(self.dmpytest0, program_id=self.program_id, nr_areas=8)
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        dms.list()
        self.assertEqual(dms[0][0].area_id, 0)
        self.assertEqual(dms[1][0].area_id, 0)
        self.assertEqual(dms[1][1].area_id, 1)
        self.assertEqual(dms[1][2].area_id, 2)
        self.assertEqual(dms[1][3].area_id, 3)
        self.assertEqual(dms[1][4].area_id, 4)
        self.assertEqual(dms[1][5].area_id, 5)
        self.assertEqual(dms[1][6].area_id, 6)
        self.assertEqual(dms[1][7].area_id, 7)
        dms.populate()
        self.assertEqual(dms[0][0].area_id, 0)
        self.assertEqual(dms[1][0].area_id, 0)
        self.assertEqual(dms[1][1].area_id, 1)
        self.assertEqual(dms[1][2].area_id, 2)
        self.assertEqual(dms[1][3].area_id, 3)
        self.assertEqual(dms[1][4].area_id, 4)
        self.assertEqual(dms[1][5].area_id, 5)
        self.assertEqual(dms[1][6].area_id, 6)
        self.assertEqual(dms[1][7].area_id, 7)

    def test_dmstatsarea_region_attr(self):
        import dmpy as dm
        import gc
        _create_stats(self.dmpytest0, program_id=self.program_id)
        _create_stats(self.dmpytest0, program_id=self.program_id, nr_areas=8)
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        dms.list()
        reg = dms[0]
        self.assertEqual(dms[0], dms[0][0].region)
        reg_str = str(reg)
        reg = None
        gc.collect()
        self.assertNotEqual(reg_str, str(dms[0]))

    #
    # Counter tests
    #
    def test_dmstats_counter_names_get_values(self):
        import dmpy as dm
        import gc
        _create_stats(self.dmpytest0, program_id=self.program_id)
        dms = dm.DmStats(self.program_id, name=self.dmpytest0)
        dms.populate(self.program_id, dm.STATS_REGIONS_ALL)
        self.assertEqual(type(dms[0][0].READS_COUNT), int)
        self.assertEqual(type(dms[0][0].READS_MERGED_COUNT), int)
        self.assertEqual(type(dms[0][0].READ_SECTORS_COUNT), int)
        self.assertEqual(type(dms[0][0].READ_NSECS), int)
        self.assertEqual(type(dms[0][0].WRITES_COUNT), int)
        self.assertEqual(type(dms[0][0].WRITES_MERGED_COUNT), int)
        self.assertEqual(type(dms[0][0].WRITE_SECTORS_COUNT), int)
        self.assertEqual(type(dms[0][0].WRITE_NSECS), int)
        self.assertEqual(type(dms[0][0].IO_IN_PROGRESS_COUNT), int)
        self.assertEqual(type(dms[0][0].IO_NSECS), int)
        self.assertEqual(type(dms[0][0].WEIGHTED_IO_NSECS), int)
        self.assertEqual(type(dms[0][0].TOTAL_READ_NSECS), int)
        self.assertEqual(type(dms[0][0].TOTAL_WRITE_NSECS), int)

# vim: set et ts=4 sw=4 :


import os
import sys
clear = lambda: os.system("cls")#clear function, clears the console screen
board = [" "," "," "," "," "," "," "," "," "]#Initialize global variables
turn = 0
player1_score=0
player2_score=0

def main():#core loop of the game, displays the beginning screen, calls other functions and displays the result of the game
    print("\nWelcome to Tic Tac Toe! To play: Place your symbol ('X' for Player 1 and 'O' for Player 2) to form a line or diagonal of 3 symbols.")
    print("Refer to the Tic Tac Toe diagram on the right to place your symbol.")
    print("To see the rules again, type : 'Help'. To restart the game, type: 'Restart'.\nTo clear the score, type: 'Clear'. To quit, type 'Quit'. To forfeit, type: 'Forfeit'. ")
    display_board([" "," "," "," "," "," "," "," "," "])
    play_loop()
    if hasWon():
        global player1_score, player2_score
        if ((turn-1)%2)+1 == 1:#If player 1 won, increment his score
            player1_score+=1
        else:#Otherwise, increment the second player's score
            player2_score+=1
        print(f"Player {((turn-1)%2)+1} Won!")
    elif isFull():
        print("It's a tie!")
    replay()

def replay():#Asks if the players want to play again, if yes: resets variables and replays, else exits program
    replay = ""
    while replay not in ("yes","y","no","n"):
        replay = input("Do you want to play again? (yes/no) : ").lower()
        if replay in ("yes","y"):
            reset()
            main()
        elif replay in ("no","n"):
            sys.exit(0)
def reset():#resets global variables
    clear()
    global board, turn
    #Modify global variables to be able to access them later in other methods
    board = [" ", " ", " ", " ", " ", " ", " ", " ", " "]
    turn = 0
def display_board(board):#Displays 2 boards, the left board displays the game being played, the right board displays the position numbers
    j=0
    print(f"\n     player 1 : {player1_score}\n     player 2 : {player2_score}\n")#When running the program in an IDE (intelliJ), a special character is printed after clear(), which offsets the first print, to fix this I print an empty line
    for i in range(11):
        if i%2==0:
            print("     |   |            |   |  ")
        elif (i-1)%4 == 0:
            print(f"   {board[j]} | {board[j+1]} | {board[j+2]}        {j+1} | {j+2} | {j+3} ")
            j+=3
        else :
            print("---------------   --------------")
    print("")
def play(pos):#Plays a X or O on the grid, depending which player is playing.
    global turn
    if turn%2==0:
        board[pos-1]="X"
    elif turn%2==1:
        board[pos-1]="O"
    turn+=1
def hasWon():#Checks if a player won
    #Check diagonals
    if (board[0]==board[4]==board[8] or board[2]==board[4]==board[6]) and board[4]!=" ":
        #It would be worth it to loop on the conditions if there were more elements
        return True
    for i in range(0,3):
            #Check columns
        if (board[i] == board[i+3] == board[i+6] and board[i]!=" "):
            #I check the 2 different conditions with different statements for code readability
            return True
            #Check lines
        elif (board[i*3]==board[i*3+1]==board[i*3+2] and board[i*3]!=" "):
            return True
    return False
def isFull():#Checks if the board is full
    for i in board:
        if i == " ":
            return False
    return True
def play_loop():#Loops while game isn't over. Takes input and either: plays, clears screen and displays or: executes command
    takeUserInput()
    if not hasWon() and not isFull() :#While game isn't over, play.
        play_loop()
def takeUserInput():#Reacts to player input
    user_input = input(f"Player {(turn % 2) + 1}, select the box to place your symbol in or enter a command : ")
    if not checkValidInt(user_input):#If the user_input isn't an int, the method won't be able to treat it, so check if it's a valid command (string)
        checkValidCommand(user_input)
def checkValidInt(user_input):#If user_input is int: return True, else return False. Treat int if possible.
    try:
        user_input = int(user_input)
        if user_input not in range(1, 10) or board[user_input-1] != " ":
            # If the position is outside the grid or is already used, send error message and continue to loop
            print("Please enter a number from 1 to 9 which hasn't been used yet.")
            return True #Go back to play_loop, don't play the invalid user input
        play(user_input)
        clear()
        display_board(board)
        return True
    except ValueError: #If user_input isn't an integer:
        return False
def checkValidCommand(user_input):#If user_input is a valid command, treat it, otherwise send error message
    global player1_score, player2_score
    if user_input.lower() == "restart":
        reset()
        main()
    elif user_input.lower() == "help":
        print(" To play: Type your symbol ('X' for Player 1 and 'O' for Player 2) to form a line or diagonal of 3 symbols. \n Commands available: 'Restart', 'Help', 'Clear', 'Quit', 'Forfeit'. ")
    elif user_input.lower() == "clear":
        player1_score=0
        player2_score=0
        clear()
        display_board(board)
    elif user_input.lower() == "quit":
        sys.exit(0)
    elif user_input.lower() == "forfeit":
        if ((turn+1)%2)+1 == 1:#If player 2 forfeited, increment player1_score
            player1_score+=1
        else:                  #If player 1 forfeited, increment player2_score
            player2_score+=1
        reset()
        main()
    else:
        print("The command you entered isn't valid, try: 'Restart', 'Help', 'Clear', 'Quit' or 'Forfeit'. ")
main()


import curses, json, sys, os.path, pyperclip

def QueryWord(word_list: list, search_term: str) -> list:
    # Ignore case
    search_term = search_term.lower()
    result = []
    for entry in word_list:
        if search_term in entry["symbol"].lower() or search_term in entry["name"].lower():
            result.append(entry)
            continue
        if "terms" in entry:
            for term in entry["terms"].split(" "):
                if search_term in term.lower():
                    result.append(entry)
                    break

    return result

# Function to run the user interface
def SearchPrompt(stdscr, word_list: list):

    # Initialize colors
    curses.use_default_colors()
    curses.init_pair(1, curses.COLOR_RED, -1)
    curses.init_pair(2, curses.COLOR_WHITE, -1)
    curses.init_pair(3, curses.COLOR_BLUE, -1)
    curses.init_pair(4, curses.COLOR_RED, curses.COLOR_WHITE)

    stdscr.clear()

    def RenderPrompt(y):
        stdscr.addstr(y, 0, " > ", curses.color_pair(1))

    def GetEntryString(entry: dict) -> str:
        name = entry["name"]
        symbol = entry["symbol"]
        return f"[{symbol}] ({name})"

    def RenderQueryResultsEllipsis(y, x):
        stdscr.addstr(y, x, "...", curses.color_pair(1))

    def RenderQueryResults(start_y, start_x, result_list):
        for i, result in enumerate(result_list):
            stdscr.addstr(start_y + i, start_x, GetEntryString(result), curses.color_pair(3))
            stdscr.clrtoeol()
            if i == 4:
                RenderQueryResultsEllipsis(start_y + 5, start_x)
                stdscr.clrtoeol()
                break

    def RenderSelection(selection_idx: int, old_selection_idx: int, last_results: list, start_y: int):
        if selection_idx != -1:
            # Update the new selection
            result = GetEntryString(last_results[selection_idx])
            # Adding an extra 1 in the Y to account for the prompt
            stdscr.addstr(start_y + selection_idx + 1, 3, f">{result}", curses.color_pair(4))
            stdscr.clrtoeol()

        if old_selection_idx < 5:
            # Reset the old selection
            last_result = GetEntryString(last_results[old_selection_idx])
            stdscr.addstr(start_y + old_selection_idx + 1, 3, f" {last_result}", curses.color_pair(3))
        elif old_selection_idx == 5:
            # Clear arrow and draw the ellipsis
            stdscr.addch(start_y + old_selection_idx + 1, 3, " ")
            RenderQueryResultsEllipsis(start_y + old_selection_idx + 1, 4)
        else:
            # Move to the old selection so that it gets cleared using clrtoeol
            stdscr.addch(start_y + old_selection_idx + 1, 3, " ")
        stdscr.clrtoeol()

    # Checks if we can fit a new query line onto the screen. If false, the screen should be cleared first
    def CheckBoundsForNewQuery():
        curses.update_lines_cols()
        return y_idx + 7 < curses.LINES

    search_term = ""
    y_idx = 1
    RenderPrompt(y_idx)
    last_results = [] # Used to keep track of how many results are currently displayed and to update selected item
    selection_idx = -1

    ALLOWED_CHARS = "()-"
    # Main user input loop
    while True:
        user_text = stdscr.getkey()
        # Used to determine whether the selection is going off screen
        curses.update_lines_cols()
        if user_text == "\n":
            word_list = QueryWord(word_list, search_term)
            y_idx += min(6, len(last_results)) # Move past result items
            y_idx += 2 # Padding between searches
            last_results = [] # Ignore what was displayed before
            search_term = ""
            selection_idx = -1

            # Clear the screen if we are going to go past the boundaries
            if not CheckBoundsForNewQuery():
                stdscr.clear()
                y_idx = 1

            RenderPrompt(y_idx)
        elif user_text == "\x1b" and len(last_results) > 0: # Escape key
            old_selection_idx = selection_idx
            selection_idx = -1
            RenderSelection(selection_idx, old_selection_idx, last_results, y_idx)
        elif user_text == "\t":
            if selection_idx == -1:
                # Display query results
                query_result = QueryWord(word_list, search_term)
                RenderQueryResults(y_idx + 1, 4, query_result)
                stdscr.clrtobot()
                last_results = query_result
            else:
                # Copy query results selection to clipboard
                selection_symbol = last_results[selection_idx]["symbol"]
                pyperclip.copy(selection_symbol)
        elif user_text == "\x08" and len(search_term) > 0: # If backspace is pressed and the seach term isn't blank
            search_term = search_term[:-1]
        elif user_text.isalnum() or user_text in ALLOWED_CHARS:
            search_term += user_text
        # Down arrow, increase selection idx
        # Only move down if within selection and not off screen
        elif user_text == "KEY_C2" and selection_idx < len(last_results) - 1 and selection_idx + y_idx + 3 < curses.LINES:
            old_selection_idx = selection_idx
            selection_idx += 1
            RenderSelection(selection_idx, old_selection_idx, last_results, y_idx)
        elif user_text == "KEY_A2" and selection_idx > -1: # Up arrow, decrease selection idx
            old_selection_idx = selection_idx
            selection_idx -= 1
            RenderSelection(selection_idx, old_selection_idx, last_results, y_idx)
        stdscr.addstr(y_idx, 3, f"{search_term}", curses.color_pair(2))
        stdscr.clrtoeol()

if __name__ != "__main__":
    print("Run this as a main file")
    sys.exit(1)

if not os.path.exists("ipa-typer.json"):
    print("The ipa-typer.json file must be included to run!")
    sys.exit(2)

with open("ipa-typer.json", "r", encoding="utf-8") as dictionary_file:
    word_list = json.load(dictionary_file)

    curses.wrapper(lambda stdscr : SearchPrompt(stdscr, word_list))


import setuptools

with open("README.md", "r") as fh:
    long_description = fh.read()

setuptools.setup(
    name="odootools-openapi",
    version="0.1.0",
    author="Loïc Faure-Lacroix <lamerstar@gmail.com>",
    author_email="lamerstar@gmail.com",
    description="Odoo Tools Rest",
    long_description=long_description,
    long_description_content_type="text/markdown",
    url="https://odoo-tools.readthedocs.io",
    project_urls={
        "Source": "https://github.com/llacroix/odoo-tools-rest",
        "Documentation": "https://odoo-tools-rest.readthedocs.io",
    },
    packages=setuptools.find_packages(),
    install_requires=[
        "openapi3",
        "requests",
        "click",
        "jinja2",
    ],
    extras_require={
        "docs": [
            "sphinx",
            "furo",
            "sphinx-argparse",
            "sphinx-click",
            "sphinxcontrib.asciinema",
        ],
        "test": [
            "mock",
            "pytest",
            "pytest-cov"
        ]
    },
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    python_requires='>=3.6',
    entry_points={
        "odootools.command.ext": [
            "gen = odootools_openapi.command:openapi"
        ]
    },
    package_data={
        "odootools_openapi": [
            "templates/*.jinja",
        ],
    }
)


from ...executers.abstractExecuter import abstractExecuter
import shutil
import os, sys, stat

class CopyDirectory(abstractExecuter):

    def _defaultParameters(self):
        return dict(
            output=["status"],
            inputs=["source","target"],
            webGUI=dict(JSONSchema="webGUI/copyDirectory_JSONchema.json",
                        UISchema  = "webGUI/copyDirectory_UISchema.json"),
            parameters={}
        )

    def run(self, **inputs):
        if (len(inputs["Source"]) > 0 and len(inputs["Target"]) > 0):
            shutil.copytree(inputs['Source'],inputs['Target'],dirs_exist_ok=inputs.get("dirs_exist_ok",True))
        else:
            print("=============== empty ===============")

        absSource = os.path.abspath(inputs["Source"])
        absTarget = os.path.abspath(inputs["Target"])

        return dict(copyDirectory="copyDirectory",
                    Source =absSource,Target=absTarget)


n = int(input())
lst = list(map(int, input().split()))

def sort1(arr):
    l = len(arr)

    for i in range(1, n):
        cur = arr[i]
        pos = i
        check = False
        while pos > 0:
            if arr[pos - 1] > cur:
                check = True
                arr[pos] = arr[pos - 1]
            else:
                break
            pos -= 1
        arr[pos] = cur
        if check:
            for j in range(len(arr)):
                print(arr[j], end=' ')
            print('')

sort1(lst)


# -*- coding: utf-8 -*-
"""
Created on Sat Feb 27 12:54:34 2021

@author: ChrisLe

NO HACE NADA PREGUNTAR / REVISAR
"""

import sys

from PyQt5.QtWidgets import QMainWindow, qApp, QMenu, QApplication

class Example (QMainWindow):

    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        self.setGeometry(450, 450, 300, 250)
        self.setWindowTitle('Context Menu')
        self.show()

    def contextMenuEvent(self, event):
        cmenu = QMenu(self)
        nuevoAct = cmenu.addAction("Nuevo")
        abriAct = cmenu.addAction("Open")
        salirAct = cmenu.addAction("Salir")
        action = cmenu.exec_(self.mapToGlobal(event.pos()))

        if action == salirAct:
            qApp.quit()

def main():
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()


import sys
import os
from github import Github

folder = str(sys.argv[1])
path = os.environ.get('Progs')      #Add project directory to env, Name it 'Progs' or change here
token = os.environ.get('GitToken')  #Add Github token to env, Name it 'GitToken' or change it here
_dir = path + '/' + folder

g=Github(token)
user = g.get_user()
login = user.login
repo = user.create_repo(folder)

#Commands to create files and perform inital commit
commands = [f'echo "# {repo.name}" >> README.md',
			f'echo "#{repo.name}" >> Main.{sys.argv[2]}',
            'git init',
            f'git remote add origin https://github.com/{login}/{folder}.git',
            'git add .',
            'git commit -m "Initial commit"',
            'git push -u origin master']

flutCommands = [f'flutter create {sys.argv[1]}',
                'git init',
                f'git remote add origin https://github.com/{login}/{folder}.git',
                'git add .',
                'git commit -m "Initial commit"',
                'git push -u origin master']

os.mkdir(_dir)
os.chdir(_dir)

if sys.argv[2] == 'flutter':
    for c in flutCommands:
        os.system(c)
else:
    for c in commands:
        os.system(c)

os.system('code .')


import gi

gi.require_version('GstApp', '1.0')
from gi.repository import GObject, Gst, GstApp

import multiprocessing
import numpy

class GstPipeline(object):
	"""runs input gstreamer pipeline"""
	def __init__(self, pipelineStr):
		super(GstPipeline, self).__init__()
		self.pipelineStr = pipelineStr

		# make pipeline object from string
		self.pipeline = Gst.parse_launch(self.pipelineStr)

		# get and configure bus
		self.bus = self.pipeline.get_bus()
		self.bus.add_signal_watch()
		self.bus.connect("message", self.onMessage)

		# create main event loop object
		self.mainloop = GObject.MainLoop()

		# thread to make pipeline non-blocking
		threadFunc = self.mainloop.run
		self.thread = multiprocessing.Process(target=threadFunc, daemon=True)

	def start(self):
		print('starting pipeline')
		self.pipeline.set_state(Gst.State.PLAYING)
		self.thread.start()

	def stop(self):
		print('stopping pipeline')
		self.pipeline.set_state(Gst.State.NULL)
		self.thread.join()
		print('stopped pipeline')

	def isPlaying(self):
		change, state, pending = self.pipeline.get_state(1)
		output = (state == Gst.State.PLAYING)
		return output

	def onMessage(self, bus, message):
		'''
		handles the messages posted on bus
		'''

		structure = message.get_structure()
		if structure is None:
			return

		if message.type == Gst.MessageType.EOS:
			self.pipeline.set_state(Gst.State.NULL)
			print('End of stream: {}'.format(message))

		elif message.type == Gst.MessageType.ERROR:
			self.pipeline.set_state(Gst.State.NULL)
			err, debug = message.parse_error()
			print("Error message: {}".format(err))
			print("Debug message: {}".format(debug))
		else:
			print("unhandled message received on bus")


import subprocess
import time

import win32gui as w
import win32con as c
import util.location as loc
import pyautogui as pg

def openChrome():
    if process_exists('chrome.exe'):
        return openWindow('Chrome')
    openApp('Chrome')

def closeChrome():
    name = 'chrome.exe'
    closeApp(name)

def openRU():
    if process_exists('RuPcTool.exe'):
        return openWindow('RU PC-TOOL')
    openApp('RUPCTOOL')

def closeRU():
    name = 'RuPcTool.exe'
    closeApp(name)

def openMUTLMU():
    if process_exists('MUTL.exe'):
        return openWindow('PC-MUTL MU')
    openApp('MU')

def openMUTLMCU():
    if process_exists('MUTL.exe'):
        return openWindow('PC-MUTL MCU')
    openApp('MCU')

def closeMUTL():
    name = 'MUTL.exe'
    closeApp(name)

def openCalibrationMenuMouse():
    openMUTLMCU()

    x, y = loc.calibration()
    if x > 0 and y > 0:
        return print('BUTTON FOUND')

    x, y = loc.right()
    if x > 0 and y > 0:
        return print('RIGHT FOUND')

def openCalibrationOptionalMenuMouse():
    openMUTLMCU()

    x, y = loc.calibrationOptional()
    if x > 0 and y > 0:
        pg.moveTo(x, y, duration=0.5)
        pg.click(x, y)
        return

    x, y = loc.right()
    if x > 0 and y > 0:
        pg.moveTo(x, y, duration=0.5)
        pg.click(x, y)
        x, y = loc.calibrationOptional()
        if x > 0 and y > 0:
            pg.moveTo(x, y, duration=0.5)
            pg.click(x, y)
            return

def startOffsetCalib():
    openCalibrationMenuMouse()
    x, y = loc.offset()
    if x <= 0 and y <= 0:
        return
    pg.moveTo(x, y, duration=0.5)
    pg.click(x, y)

def startDefectCalib():
    openCalibrationMenuMouse()
    x, y = loc.defect()
    if x <= 0 and y <= 0:
        return
    pg.moveTo(x, y, duration=0.5)
    pg.click(x, y)

def startDefectSolidCalib():
    openCalibrationMenuMouse()
    x, y = loc.defectSolid()
    if x <= 0 and y <= 0:
        return
    pg.moveTo(x, y, duration=0.5)
    pg.click(x, y)

def startPixelDefectCalib():
    openCalibrationMenuMouse()
    x, y = loc.pixelDefect()
    if x <= 0 and y <= 0:
        return
    pg.moveTo(x, y, duration=0.5)
    pg.click(x, y)

def startShadingCalib():
    openCalibrationMenuMouse()
    x, y = loc.shading()
    if x <= 0 and y <= 0:
        return
    pg.moveTo(x, y, duration=0.5)
    pg.click(x, y)

def startUniformityCalib():
    openCalibrationMenuMouse()
    x, y = loc.shading()
    if x <= 0 and y <= 0:
        return
    pg.moveTo(x, y, duration=0.5)
    pg.click(x, y)

def startDefectSolidStereoCalib():
    openCalibrationMenuMouse()
    x, y = loc.defectSolidStereo()
    if x <= 0 and y <= 0:
        return
    pg.moveTo(x, y, duration=0.5)
    pg.click(x, y)

def startDefectSolidBpyCalib():
    openCalibrationMenuMouse()
    x, y = loc.defectSolidBpy()
    if x <= 0 and y <= 0:
        return
    pg.moveTo(x, y, duration=0.5)
    pg.click(x, y)

def startDefectSolidTomo():
    openCalibrationMenuMouse()
    x, y = loc.defectSolidTomo()
    if x <= 0 and y <= 0:
        return
    pg.moveTo(x, y, duration=0.5)
    pg.click(x, y)

def startUniformityStereoCalib():
    openCalibrationMenuMouse()
    x, y = loc.offset()
    if x <= 0 and y <= 0:
        return
    pg.moveTo(x, y, duration=0.5)
    pg.click(x, y)

def startUniformityBpyCalib():
    openCalibrationMenuMouse()
    x, y = loc.uniformityBpy()
    if x <= 0 and y <= 0:
        return
    pg.moveTo(x, y, duration=0.5)
    pg.click(x, y)

def startUniformityTomoCalib():
    openCalibrationMenuMouse()
    x, y = loc.uniformityTomo()
    if x <= 0 and y <= 0:
        return
    pg.moveTo(x, y, duration=0.5)
    pg.click(x, y)

def startUniformityESCalib():
    openCalibrationMenuMouse()
    x, y = loc.uniformityES()
    if x <= 0 and y <= 0:
        return
    pg.moveTo(x, y, duration=0.5)
    pg.click(x, y)
# __________________________________________________________________

def openWindow(name):
    def handler(hwnd, active):
        if w.IsWindowVisible(hwnd):
            wname = w.GetWindowText(hwnd)
            if active in wname:
                w.SetForegroundWindow(hwnd)
                w.ShowWindow(hwnd, c.SW_NORMAL)
    w.EnumWindows(handler, name)

def scanWindows():
    file = open("windowNames.txt", "w")
    def handler(hwnd, param):
        if w.IsWindowVisible(hwnd):
            wname = w.GetWindowText(hwnd)
            size = len(wname)
            if size > 0:
                file.writelines(wname+'\n')
    w.EnumWindows(handler, None)
    file.close()

def process_exists(process_name):
    call = 'TASKLIST', '/FI', 'imagename eq %s' % process_name
    # use buildin check_output right away
    output = subprocess.check_output(call)
    # print(output)
    output = output.decode('latin-1')
    # check in last line for process name
    last_line = output.strip().split('\r\n')[-1]
    #print(last_line)
    # print(last_line)
    # because Fail message could be translated
    return last_line.lower().startswith(process_name.lower())

def openApp(appName):
    try:
        if appName == 'RUPCTOOL':
            route = 'C:\Program Files\Fujifilm\FCR\TOOL\RuPcTool\\'
            exe = 'RuPcTool'
            args = ''
            subprocess.Popen(route + exe + args)

        elif appName == 'MU':
            route = 'C:\Program Files\FujiFilm\FCR\TOOL\MUTL\\'
            exe = 'MUTL'
            args = ' /IP:192.168.0.100 /RUNAME:MU0 /TYPE:FDR-2500A'
            subprocess.Popen(route + exe + args)

        elif appName == 'MCU':
            route = 'C:\Program Files\FujiFilm\FCR\TOOL\MUTL\\'
            exe = 'MUTL'
            args = ' /IP:192.168.0.101 /RUNAME:MCU0 /TYPE:FDR-3000DRL /FCR:C:\Program Files\FujiFilm\FCR\\'
            subprocess.Popen(route + exe + args)

    except FileNotFoundError:
        print('File not found')

def closeApp(appName):
    if process_exists(appName):
        subprocess.call(["taskkill", "/F", "/IM", appName],
                        stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def getMacList():
    call = 'chcp 65001 | getmac /fo csv /v /nh'
    output = subprocess.check_output(call, shell=True)
    output = output.decode('utf-8').split('\r\n')
    ethernet = []
    for index, line in enumerate(output):
        items = line.split(',')
        if "Ethernet" in items[0]:
            ethernet.append(items[2].replace('"', '').replace('-', ':'))
    # print(ethernet)
    return '\n'.join(ethernet)

def getMac(ip):
    try:
        call = 'arp', '-a', ip
        output = subprocess.check_output(call)
        output = output.decode('latin-1').strip().split('\r\n')
        lines = output[3:]
        for item in lines:
            line = []
            print(item)
            subarray = item.strip().split(' ')
            for subitem in subarray:
                line.append(subitem) if len(subitem) > 0 else 0
            if line[0] == ip:
                return line[1].replace('-',':')
        return 'IP address not found!'
    except IndexError:
        print(output)

def getMac(ip):
    call = 'arp', '-a', ip
    output = subprocess.check_output(call)
    output = output.decode('latin-1').strip().split('\r\n')
    lastLine = output[2:][0].split(' ')
    for item in lastLine:
        if len(item) > 0 and '-' in item:
            return item.upper().replace('-', ':')
    return 'IP not found'

def getMCUMac():
    return getMac('192.168.0.101')

def saveMACs():
    other = getMacList()
    mcu = getMCUMac()
    f = open('macList.txt', 'w')
    f.writelines('LOCAL:\n')
    f.writelines(other+'\n')
    f.writelines('MCU0:\n')
    f.writelines(mcu)
    f.close()


# o exercício é basicamente: [{[]}] lista dentro de dict dentro de lista
banco = list()
jogador = dict()
escolha = 's'
escolhadados = 0
while escolha in 'Ss':
    jogador['nome'] = str(input('Nome do Jogador: '))
    partidas = int(input(f'Quantas partidas {jogador["nome"]} jogou? '))
    jogador['gols'] = list()
    for jogo in range(0, partidas):
        gol = int(input(f'Quantos gols na partida {jogo+1}? '))
        jogador['gols'].append(gol)
    jogador['total'] = sum(jogador['gols'])
    escolha = str(input('Quer continuar? [S/N] '))
    banco.append(jogador.copy())
print('Cód', end='  ')
for k in banco[0]:
    print(f'{k:<15}', end='')
print()
for k, v in enumerate(banco):
    print(f'{k:^5}', end='')
    for d in v.values():
        print(f'{str(d):<15}', end='')
    print()
# banco = [{'nome': lucas, 'gols': [1, 2, 3], 'total':6}, {}]
while escolhadados != 999:
    escolhadados = int(input('Mostrar dados de qual jogador? (999 para parar) '))
    if escolhadados == 999:
        break
    print(f'LEVANTAMENTO DO JOGADOR {banco[escolhadados]["nome"]}')
    for k in range(0, len(banco[escolhadados]['gols'])):
        print(f'No jogo {k+1} fez {banco[escolhadados]["gols"][k]} gols.')


from enum import Enum
from typing import Any
from uuid import uuid4

from fastapi.responses import JSONResponse
from core.constants.response_messages import ResponseConstants

class Language(str, Enum):
    ar: str = "ar"
    en: str = "en"

def http_response(message, status, language: Language = "ar", data: Any = None,
                  request_id: str = None, meta: Any = None):
    if not 200 <= status <= 299:
        return http_error_response(error_message=message, status=status, language=language)

    mapper = ResponseConstants.messages_dict()

    if isinstance(message, str):
        try:
            message = mapper[message][language]
        except (KeyError, TypeError):
            pass
    else:
        try:
            message = message[language]
        except (KeyError, TypeError):
            pass

    response = {
        "status": str(status),
        "message": message,
        "data": data if data else [],
        "meta": meta if meta else {},
        "request-id": request_id if request_id else generate_request_id()
    }

    headers = {}
    try:
        headers['x-resource-id'] = str(data.get('id', ''))
    except Exception as e:  # noqa
        headers['x-resource-id'] = ''

    return JSONResponse(status_code=status, content=response, headers=headers)

def http_error_response(error_message, status, language="en", request_id: str = None):
    if 400 <= status <= 499:
        try:
            error_message = error_message[language]
        except (KeyError, TypeError):
            if not isinstance(error_message, str):
                error_message = "undefined message"

    response = {
        "status": str(status),
        "message": error_message,
        "request-id": request_id if request_id else generate_request_id()
    }
    return JSONResponse(status_code=status, content=response)

def generate_request_id():
    return "dev-" + str(uuid4())


# -*- encoding:utf-8 -*-
from Process.CCSLToMyCCSL import CCSLToMyCCSL

def ProcessCCSL(inputMyCCSLstr):
    # 传入的每条CCSL元素包含着用";"隔开的各种MyCCSL语句
    tmpList = inputMyCCSLstr.split(";")
    text = ""
    for i,tmpstr in enumerate(tmpList):
        tmpList[i] = tmpstr.strip()
    for i,tmpstr in enumerate(tmpList):
        if tmpstr and not tmpstr.isspace():
            text = text + tmpstr + "\n"
    return text


class Solution(object):
    def solve(self, board):
        """
        :type board: List[List[str]]
        :rtype: void Do not return anything, modify board in-place instead.
        """
        if not board: return
        m, n = len(board), len(board[0])

        for i in range(m):
            board[i] = [board[i][j] for j in range(n)]

        s = []
        for i in range(m):
            s += (i, 0), (i, n-1)
        for j in range(n):
            s += (0, j), (m-1, j)
        while s:  # beautiful part
            i, j = s.pop()
            if 0 <= i < m and 0 <= j < n and board[i][j] == "O":
                board[i][j] = "G"
                s += (i+1, j), (i-1, j), (i, j+1), (i, j-1)
        board[:] = [["O" if board[i][j] == "G" else "X" for j in range(n)] for i in range(m)]

s=Solution()
s.solve(["XOXOOOO",
         "XOOOOOO",
         "XOOOOXO",
         "OOOOXOX",
         "OXOOOOO",
         "OOOOOOO",
         "OXOOOOO"])


# --------- 캡처 함수  --------
import time
from io import BytesIO

from PIL import Image
from selenium.webdriver.support import expected_conditions as EC

from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait

from capture_api.utils.selenium_config import elementExclude, settingDriverSize, screenShot, autoMbFaceBookLogin

def mobile_capture(data, driver):
    '''
    Post 요청을 통해 이미지 크기, 너비, url, 파일 이름을 받아온 뒤
    셀레니움 라이브러리를 통해 캡쳐를 진행
    '''
    # 요청 데이터 파싱
    # path = "/Users/marmin/Downloads/chromedriver_mac_arm64/chromedriver"

    url = data.get("urlPath",None)
    # # 전송된 데이터 중 filename을 가져옴
    filename = data.get("filename", None)
    print("파싱 성공")
    # # 파일 이름 중복 방지
    a = url.split(".")
    print(a)
    # -----------------------FACEBOOK---------------------------------------
    if a[1] == "facebook":
        print("페이스북 입니다")
        width = 600
        height = 850
        settingDriverSize(driver, width, height)
        # ------- Login -------
        autoMbFaceBookLogin(driver, "01095528693", "thpo4327")
        driver.get(url)
        print("크롬 생성후 url 끌어오기 성공")
        a = url.split(".")
        if "photo/?fbid" in a[2]:
            # 이미지 창일 경우에 다른 랜딩페이지를 캡쳐하도록 하는 로직 작성
            print("캡쳐불가 => 랜딩 페이지로 리디렉트")
            target_substring = "https://www.facebook.com/"
            # 모든 <a> 태그 찾기
            x_elements = driver.find_elements(By.XPATH, "//div/div/button")
            for x in x_elements:
                data_action_id = x.get_attribute("data-action-id")
                if data_action_id == 16:
                    x.click()
            print(driver.current_url)
            wait = WebDriverWait(driver, 10)
            element = wait.until(EC.presence_of_element_located((By.TAG_NAME, "img")))
            time.sleep(2)
            # 캡쳐할 랜딩 페이지일 경우에 캡쳐하는 로직 작성
        wait = WebDriverWait(driver, 10)
        element = wait.until(EC.presence_of_element_located((By.TAG_NAME, "img")))
        driver.implicitly_wait(3)
        # elementExclude(driver, "x1s65kcs")
        elementExclude(driver,xpath='//*[@role="navigation"]')

        screenShot(filename, driver, url)
    # -----------------------YOUTUBE---------------------------------------
    elif a[1] == "youtube":
        print("유튜브 입니다")
    #     # todo => 유뷰트 관련 로직 작성/ 클래스값 확인 / 스크롤바 옵션 추가하기
        wait = WebDriverWait(driver, 10)
        element = EC.presence_of_all_elements_located((By.CLASS_NAME, "yt-img-shadow"))
        driver.implicitly_wait(4)
        width = 500
        height = 850
        settingDriverSize(driver,width,height)
        screenShot(filename, driver)
        driver.get(url)
        wait = WebDriverWait(driver, 10)
        element = EC.presence_of_all_elements_located((By.CLASS_NAME, "yt-img-shadow"))
        time.sleep(4)
        # 스크린샷 캡처
        screenShot(filename, driver, url)
    # -------------------------- Instagram -------------------------------
    elif a[1] == "instagram":
        driver.get(url)
        wait = WebDriverWait(driver, 20)
        element = wait.until(EC.presence_of_element_located((By.TAG_NAME, "canvas")))
        time.sleep(4)
        print("인스타그램 입니다")
    #     # todo => 인스타그램 관련 로직 작성/ 클래스값 확인/ 옵션 추가하기
        # elementExclude(driver, xpath= "//span//div//a")
        elementExclude(driver=driver,className="_acun")
        elementExclude(driver= driver,className="_ab8q")
        try:
            # X 버튼을 찾습니다.
            x_button = driver.find_element(By.CLASS_NAME, "_abn5")
            # X 버튼을 클릭합니다.
            x_button.click()
        except:
            # 엘리먼트를 찾지 못한 경우에 대한 예외 처리
            print("X 버튼을 찾을 수 없습니다.")

        element = wait.until(EC.presence_of_element_located((By.TAG_NAME, "canvas")))
        width = 500
        height = 1100
        settingDriverSize(driver,width, height)
        filepath = screenShot(filename,driver,url)
        with open(filepath, 'rb') as f:
            image = Image.open(BytesIO(f.read()))
        cropped = image.crop((0,0,500,700))
        cropped.save(filepath)
    # -----------------------ETC---------------------------------------

    else:
        driver.get(url)
        wait = WebDriverWait(driver, 20)
        element = wait.until(EC.presence_of_element_located((By.TAG_NAME, "img")))
        time.sleep(1)
        width = 500
        height = 880
        print('스크롤 설정 성공')
        driver.set_window_size(width, height)
        print("윈도우 사이즈 설정 성공")
        screenShot(filename, driver,url)
    return '캡처 성공'

# ------------ api 함수 ------------


from itertools import permutations

print(permutations(range(5), 3))

def nPr(n, r, k):
    """
    n개 중 r개를 뽑는 순열
    - n: 원소의 전체 개수
    - r: 뽑아야 하는 개수
    - k: 현재까지 뽑은 원소의 개수
    """
    if k == r:
        print(case)
    else:
        for i in range(n):      # 모든 원소 순회
            if not used[i]:     # 사용된 적이 없으면
                used[i] = True  # 사용됨으로 표시
                case[k] = arr[i]  # 순열에 사용
                nPr(n, r, k + 1)
                used[i] = False # 다른 자리에서 사용 가능

N = 5
R = 3
arr = list(range(N))    # 순열에 쓰일 후보 목록
used = [False] * N      # 사용 여부
case = [0] * R          # 순열을 저장하는 배열
nPr(N, 3, 0)

def nTTr(n, r, k):
    """
    중복순열 = 순열 - 방문 체크 부분 제거 => 재방문 가능
    - n: 원소의 전체 개수
    - k: 현재 레벨
    """
    if k == r:
        print(case)
    else:
        for i in range(n):          # 모든 원소 순회
                case[k] = arr[i]    # 순열에 사용
                nTTr(n, r, k + 1)

N = 3
arr = list(range(N))
case = [0] * N
nTTr(N, N, 0)