# -*- coding: utf-8 -*-
'''
Tests for the zfs utils library

:codeauthor:    Jorge Schrauwen <sjorge@blackdot.be>
:maintainer:    Jorge Schrauwen <sjorge@blackdot.be>
:maturity:      new
:platform:      illumos,freebsd,linux

.. versionadded:: 2018.3.1
'''

# Import Python libs
from __future__ import absolute_import, unicode_literals, print_function

# Import Salt Testing libs
from tests.support.unit import skipIf, TestCase
from tests.support.mock import (
    MagicMock,
    patch,
    NO_MOCK,
    NO_MOCK_REASON,
)

# Import Salt Execution module to test
import salt.utils.zfs as zfs

# Import Salt Utils
import salt.loader
from salt.utils.odict import OrderedDict

# property_map mocks
pmap_exec_zpool = {
    'retcode': 2,
    'stdout': '',
    'stderr': "\n".join([
        'missing property argument',
        'usage:',
        '        get [-Hp] [-o "all" | field[,...]] <"all" | property[,...]> <pool> ...',
        '',
        'the following properties are supported:',
        '',
        '        PROPERTY         EDIT   VALUES',
        '',
        '        allocated          NO   <size>',
        '        capacity           NO   <size>',
        '        dedupratio         NO   <1.00x or higher if deduped>',
        '        expandsize         NO   <size>',
        '        fragmentation      NO   <percent>',
        '        free               NO   <size>',
        '        freeing            NO   <size>',
        '        guid               NO   <guid>',
        '        health             NO   <state>',
        '        leaked             NO   <size>',
        '        size               NO   <size>',
        '        altroot           YES   <path>',
        '        autoexpand        YES   on | off',
        '        autoreplace       YES   on | off',
        '        bootfs            YES   <filesystem>',
        '        bootsize          YES   <size>',
        '        cachefile         YES   <file> | none',
        '        comment           YES   <comment-string>',
        '        dedupditto        YES   <threshold (min 100)>',
        '        delegation        YES   on | off',
        '        failmode          YES   wait | continue | panic',
        '        listsnapshots     YES   on | off',
        '        readonly          YES   on | off',
        '        version           YES   <version>',
        '        feature@...       YES   disabled | enabled | active',
        '',
        'The feature@ properties must be appended with a feature name.',
        'See zpool-features(5). ',
    ]),
}
pmap_zpool = {
  'comment': {
    'edit': True,
    'type': 'str',
    'values': '<comment-string>'
  },
  'freeing': {
    'edit': False,
    'type': 'size',
    'values': '<size>'
  },
  'listsnapshots': {
    'edit': True,
    'type': 'bool',
    'values': 'on | off'
  },
  'leaked': {
    'edit': False,
    'type': 'size',
    'values': '<size>'
  },
  'version': {
    'edit': True,
    'type': 'numeric',
    'values': '<version>'
  },
  'write': {
    'edit': False,
    'type': 'size',
    'values': '<size>'
  },
  'replace': {
    'edit': True,
    'type': 'bool',
    'values': 'on | off'
  },
  'delegation': {
    'edit': True,
    'type': 'bool',
    'values': 'on | off'
  },
  'dedupditto': {
    'edit': True,
    'type': 'str',
    'values': '<threshold (min 100)>'
  },
  'autoexpand': {
    'edit': True,
    'type': 'bool',
    'values': 'on | off'
  },
  'alloc': {
    'edit': False,
    'type': 'size',
    'values': '<size>'
  },
  'allocated': {
    'edit': False,
    'type': 'size',
    'values': '<size>'
  },
  'guid': {
    'edit': False,
    'type': 'numeric',
    'values': '<guid>'
  },
  'size': {
    'edit': False,
    'type': 'size',
    'values': '<size>'
  },
  'cap': {
    'edit': False,
    'type': 'numeric',
    'values': '<count>'
  },
  'capacity': {
    'edit': False,
    'type': 'size',
    'values': '<size>'
  },
  "capacity-alloc": {
    "edit": False,
    "type": "size",
    "values": "<size>"
  },
  "capacity-free": {
    "edit": False,
    "type": "size",
    "values": "<size>"
  },
  'cachefile': {
    'edit': True,
    'type': 'str',
    'values': '<file> | none'
  },
  "cksum": {
    "edit": False,
    "type": "numeric",
    "values": "<count>"
  },
  'bootfs': {
    'edit': True,
    'type': 'str',
    'values': '<filesystem>'
  },
  'autoreplace': {
    'edit': True,
    'type': 'bool',
    'values': 'on | off'
  },
  "bandwith-read": {
    "edit": False,
    "type": "size",
    "values": "<size>"
  },
  "bandwith-write": {
    "edit": False,
    "type": "size",
    "values": "<size>"
  },
  "operations-read": {
    "edit": False,
    "type": "size",
    "values": "<size>"
  },
  "operations-write": {
    "edit": False,
    "type": "size",
    "values": "<size>"
  },
  "read": {
    "edit": False,
    "type": "size",
    "values": "<size>"
  },
  'readonly': {
    'edit': True,
    'type': 'bool',
    'values': 'on | off'
  },
  'dedupratio': {
    'edit': False,
    'type': 'str',
    'values': '<1.00x or higher if deduped>'
  },
  'health': {
    'edit': False,
    'type': 'str',
    'values': '<state>'
  },
  'feature@': {
    'edit': True,
    'type': 'str',
    'values': 'disabled | enabled | active'
  },
  'expandsize': {
    'edit': False,
    'type': 'size',
    'values': '<size>'
  },
  'listsnaps': {
    'edit': True,
    'type': 'bool',
    'values': 'on | off'
  },
  'bootsize': {
    'edit': True,
    'type': 'size',
    'values': '<size>'
  },
  'free': {
    'edit': False,
    'type': 'size',
    'values': '<size>'
  },
  'failmode': {
    'edit': True,
    'type': 'str',
    'values': 'wait | continue | panic'
  },
  'altroot': {
    'edit': True,
    'type': 'str',
    'values': '<path>'
  },
  'expand': {
    'edit': True,
    'type': 'bool',
    'values': 'on | off'
  },
  'frag': {
    'edit': False,
    'type': 'str',
    'values': '<percent>'
  },
  'fragmentation': {
    'edit': False,
    'type': 'str',
    'values': '<percent>'
  }
}
pmap_exec_zfs = {
    'retcode': 2,
    'stdout': '',
    'stderr': "\n".join([
        'missing property argument',
        'usage:',
        '        get [-crHp] [-d max] [-o "all" | field[,...]]',
        '            [-t type[,...]] [-s source[,...]]',
        '            <"all" | property[,...]> [filesystem|volume|snapshot|bookmark] ...',
        '',
        'The following properties are supported:',
        '',
        '        PROPERTY       EDIT  INHERIT   VALUES',
        '',
        '        available        NO       NO   <size>',
        '        clones           NO       NO   <dataset>[,...]',
        '        compressratio    NO       NO   <1.00x or higher if compressed>',
        '        creation         NO       NO   <date>',
        '        defer_destroy    NO       NO   yes | no',
        '        filesystem_count  NO       NO   <count>',
        '        logicalreferenced  NO       NO   <size>',
        '        logicalused      NO       NO   <size>',
        '        mounted          NO       NO   yes | no',
        '        origin           NO       NO   <snapshot>',
        '        receive_resume_token  NO       NO   <string token>',
        '        refcompressratio  NO       NO   <1.00x or higher if compressed>',
        '        referenced       NO       NO   <size>',
        '        snapshot_count   NO       NO   <count>',
        '        type             NO       NO   filesystem | volume | snapshot | bookmark',
        '        used             NO       NO   <size>',
        '        usedbychildren   NO       NO   <size>',
        '        usedbydataset    NO       NO   <size>',
        '        usedbyrefreservation  NO       NO   <size>',
        '        usedbysnapshots  NO       NO   <size>',
        '        userrefs         NO       NO   <count>',
        '        written          NO       NO   <size>',
        '        aclinherit      YES      YES   discard | noallow | restricted | passthrough | passthrough-x',
        '        aclmode         YES      YES   discard | groupmask | passthrough | restricted',
        '        atime           YES      YES   on | off',
        '        canmount        YES       NO   on | off | noauto',
        '        casesensitivity  NO      YES   sensitive | insensitive | mixed',
        '        checksum        YES      YES   on | off | fletcher2 | fletcher4 | sha256 | sha512 | skein | edonr',
        '        compression     YES      YES   on | off | lzjb | gzip | gzip-[1-9] | zle | lz4',
        '        copies          YES      YES   1 | 2 | 3',
        '        dedup           YES      YES   on | off | verify | sha256[,verify], sha512[,verify], skein[,verify], edonr,verify',
        '        devices         YES      YES   on | off',
        '        exec            YES      YES   on | off',
        '        filesystem_limit YES       NO   <count> | none',
        '        logbias         YES      YES   latency | throughput',
        '        mlslabel        YES      YES   <sensitivity label>',
        '        mountpoint      YES      YES   <path> | legacy | none',
        '        nbmand          YES      YES   on | off',
        '        normalization    NO      YES   none | formC | formD | formKC | formKD',
        '        primarycache    YES      YES   all | none | metadata',
        '        quota           YES       NO   <size> | none',
        '        readonly        YES      YES   on | off',
        '        recordsize      YES      YES   512 to 1M, power of 2',
        '        redundant_metadata YES      YES   all | most',
        '        refquota        YES       NO   <size> | none',
        '        refreservation  YES       NO   <size> | none',
        '        reservation     YES       NO   <size> | none',
        '        secondarycache  YES      YES   all | none | metadata',
        '        setuid          YES      YES   on | off',
        '        sharenfs        YES      YES   on | off | share(1M) options',
        '        sharesmb        YES      YES   on | off | sharemgr(1M) options',
        '        snapdir         YES      YES   hidden | visible',
        '        snapshot_limit  YES       NO   <count> | none',
        '        sync            YES      YES   standard | always | disabled',
        '        utf8only         NO      YES   on | off',
        '        version         YES       NO   1 | 2 | 3 | 4 | 5 | current',
        '        volblocksize     NO      YES   512 to 128k, power of 2',
        '        volsize         YES       NO   <size>',
        '        vscan           YES      YES   on | off',
        '        xattr           YES      YES   on | off',
        '        zoned           YES      YES   on | off',
        '        userused@...     NO       NO   <size>',
        '        groupused@...    NO       NO   <size>',
        '        userquota@...   YES       NO   <size> | none',
        '        groupquota@...  YES       NO   <size> | none',
        '        written@<snap>   NO       NO   <size>',
        '',
        'Sizes are specified in bytes with standard units such as K, M, G, etc.',
        '',
        'User-defined properties can be specified by using a name containing a colon (:).',
        '',
        'The {user|group}{used|quota}@ properties must be appended with',
        'a user or group specifier of one of these forms:',
        '    POSIX name      (eg: "matt")',
        '    POSIX id        (eg: "126829")',
        '    SMB name@domain (eg: "matt@sun")',
        '    SMB SID         (eg: "S-1-234-567-89")',
    ]),
}
pmap_zfs = {
  "origin": {
    "edit": False,
    "inherit": False,
    "values": "<snapshot>",
    "type": "str"
  },
  "setuid": {
    "edit": True,
    "inherit": True,
    "values": "on | off",
    "type": "bool"
  },
  "referenced": {
    "edit": False,
    "inherit": False,
    "values": "<size>",
    "type": "size"
  },
  "vscan": {
    "edit": True,
    "inherit": True,
    "values": "on | off",
    "type": "bool"
  },
  "logicalused": {
    "edit": False,
    "inherit": False,
    "values": "<size>",
    "type": "size"
  },
  "userrefs": {
    "edit": False,
    "inherit": False,
    "values": "<count>",
    "type": "numeric"
  },
  "primarycache": {
    "edit": True,
    "inherit": True,
    "values": "all | none | metadata",
    "type": "str"
  },
  "logbias": {
    "edit": True,
    "inherit": True,
    "values": "latency | throughput",
    "type": "str"
  },
  "creation": {
    "edit": False,
    "inherit": False,
    "values": "<date>",
    "type": "str"
  },
  "sync": {
    "edit": True,
    "inherit": True,
    "values": "standard | always | disabled",
    "type": "str"
  },
  "dedup": {
    "edit": True,
    "inherit": True,
    "values": "on | off | verify | sha256[,verify], sha512[,verify], skein[,verify], edonr,verify",
    "type": "bool"
  },
  "sharenfs": {
    "edit": True,
    "inherit": True,
    "values": "on | off | share(1m) options",
    "type": "bool"
  },
  "receive_resume_token": {
    "edit": False,
    "inherit": False,
    "values": "<string token>",
    "type": "str"
  },
  "usedbyrefreservation": {
    "edit": False,
    "inherit": False,
    "values": "<size>",
    "type": "size"
  },
  "sharesmb": {
    "edit": True,
    "inherit": True,
    "values": "on | off | sharemgr(1m) options",
    "type": "bool"
  },
  "rdonly": {
    "edit": True,
    "inherit": True,
    "values": "on | off",
    "type": "bool"
  },
  "reservation": {
    "edit": True,
    "inherit": False,
    "values": "<size> | none",
    "type": "size"
  },
  "reserv": {
    "edit": True,
    "inherit": False,
    "values": "<size> | none",
    "type": "size"
  },
  "mountpoint": {
    "edit": True,
    "inherit": True,
    "values": "<path> | legacy | none",
    "type": "str"
  },
  "casesensitivity": {
    "edit": False,
    "inherit": True,
    "values": "sensitive | insensitive | mixed",
    "type": "str"
  },
  "utf8only": {
    "edit": False,
    "inherit": True,
    "values": "on | off",
    "type": "bool"
  },
  "usedbysnapshots": {
    "edit": False,
    "inherit": False,
    "values": "<size>",
    "type": "size"
  },
  "readonly": {
    "edit": True,
    "inherit": True,
    "values": "on | off",
    "type": "bool"
  },
  "written@": {
    "edit": False,
    "inherit": False,
    "values": "<size>",
    "type": "size"
  },
  "avail": {
    "edit": False,
    "inherit": False,
    "values": "<size>",
    "type": "size"
  },
  "recsize": {
    "edit": True,
    "inherit": True,
    "values": "512 to 1m, power of 2",
    "type": "str"
  },
  "atime": {
    "edit": True,
    "inherit": True,
    "values": "on | off",
    "type": "bool"
  },
  "compression": {
    "edit": True,
    "inherit": True,
    "values": "on | off | lzjb | gzip | gzip-[1-9] | zle | lz4",
    "type": "bool"
  },
  "snapdir": {
    "edit": True,
    "inherit": True,
    "values": "hidden | visible",
    "type": "str"
  },
  "aclmode": {
    "edit": True,
    "inherit": True,
    "values": "discard | groupmask | passthrough | restricted",
    "type": "str"
  },
  "zoned": {
    "edit": True,
    "inherit": True,
    "values": "on | off",
    "type": "bool"
  },
  "copies": {
    "edit": True,
    "inherit": True,
    "values": "1 | 2 | 3",
    "type": "numeric"
  },
  "snapshot_limit": {
    "edit": True,
    "inherit": False,
    "values": "<count> | none",
    "type": "numeric"
  },
  "aclinherit": {
    "edit": True,
    "inherit": True,
    "values": "discard | noallow | restricted | passthrough | passthrough-x",
    "type": "str"
  },
  "compressratio": {
    "edit": False,
    "inherit": False,
    "values": "<1.00x or higher if compressed>",
    "type": "str"
  },
  "xattr": {
    "edit": True,
    "inherit": True,
    "values": "on | off",
    "type": "bool"
  },
  "written": {
    "edit": False,
    "inherit": False,
    "values": "<size>",
    "type": "size"
  },
  "version": {
    "edit": True,
    "inherit": False,
    "values": "1 | 2 | 3 | 4 | 5 | current",
    "type": "numeric"
  },
  "recordsize": {
    "edit": True,
    "inherit": True,
    "values": "512 to 1m, power of 2",
    "type": "str"
  },
  "refquota": {
    "edit": True,
    "inherit": False,
    "values": "<size> | none",
    "type": "size"
  },
  "filesystem_limit": {
    "edit": True,
    "inherit": False,
    "values": "<count> | none",
    "type": "numeric"
  },
  "lrefer.": {
    "edit": False,
    "inherit": False,
    "values": "<size>",
    "type": "size"
  },
  "type": {
    "edit": False,
    "inherit": False,
    "values": "filesystem | volume | snapshot | bookmark",
    "type": "str"
  },
  "secondarycache": {
    "edit": True,
    "inherit": True,
    "values": "all | none | metadata",
    "type": "str"
  },
  "refer": {
    "edit": False,
    "inherit": False,
    "values": "<size>",
    "type": "size"
  },
  "available": {
    "edit": False,
    "inherit": False,
    "values": "<size>",
    "type": "size"
  },
  "used": {
    "edit": False,
    "inherit": False,
    "values": "<size>",
    "type": "size"
  },
  "exec": {
    "edit": True,
    "inherit": True,
    "values": "on | off",
    "type": "bool"
  },
  "compress": {
    "edit": True,
    "inherit": True,
    "values": "on | off | lzjb | gzip | gzip-[1-9] | zle | lz4",
    "type": "bool"
  },
  "volblock": {
    "edit": False,
    "inherit": True,
    "values": "512 to 128k, power of 2",
    "type": "str"
  },
  "refcompressratio": {
    "edit": False,
    "inherit": False,
    "values": "<1.00x or higher if compressed>",
    "type": "str"
  },
  "quota": {
    "edit": True,
    "inherit": False,
    "values": "<size> | none",
    "type": "size"
  },
  "groupquota@": {
    "edit": True,
    "inherit": False,
    "values": "<size> | none",
    "type": "size"
  },
  "userquota@": {
    "edit": True,
    "inherit": False,
    "values": "<size> | none",
    "type": "size"
  },
  "snapshot_count": {
    "edit": False,
    "inherit": False,
    "values": "<count>",
    "type": "numeric"
  },
  "volsize": {
    "edit": True,
    "inherit": False,
    "values": "<size>",
    "type": "size"
  },
  "clones": {
    "edit": False,
    "inherit": False,
    "values": "<dataset>[,...]",
    "type": "str"
  },
  "canmount": {
    "edit": True,
    "inherit": False,
    "values": "on | off | noauto",
    "type": "bool"
  },
  "mounted": {
    "edit": False,
    "inherit": False,
    "values": "yes | no",
    "type": "bool_alt"
  },
  "groupused@": {
    "edit": False,
    "inherit": False,
    "values": "<size>",
    "type": "size"
  },
  "normalization": {
    "edit": False,
    "inherit": True,
    "values": "none | formc | formd | formkc | formkd",
    "type": "str"
  },
  "usedbychildren": {
    "edit": False,
    "inherit": False,
    "values": "<size>",
    "type": "size"
  },
  "usedbydataset": {
    "edit": False,
    "inherit": False,
    "values": "<size>",
    "type": "size"
  },
  "mlslabel": {
    "edit": True,
    "inherit": True,
    "values": "<sensitivity label>",
    "type": "str"
  },
  "refreserv": {
    "edit": True,
    "inherit": False,
    "values": "<size> | none",
    "type": "size"
  },
  "defer_destroy": {
    "edit": False,
    "inherit": False,
    "values": "yes | no",
    "type": "bool_alt"
  },
  "volblocksize": {
    "edit": False,
    "inherit": True,
    "values": "512 to 128k, power of 2",
    "type": "str"
  },
  "lused.": {
    "edit": False,
    "inherit": False,
    "values": "<size>",
    "type": "size"
  },
  "redundant_metadata": {
    "edit": True,
    "inherit": True,
    "values": "all | most",
    "type": "str"
  },
  "filesystem_count": {
    "edit": False,
    "inherit": False,
    "values": "<count>",
    "type": "numeric"
  },
  "devices": {
    "edit": True,
    "inherit": True,
    "values": "on | off",
    "type": "bool"
  },
  "refreservation": {
    "edit": True,
    "inherit": False,
    "values": "<size> | none",
    "type": "size"
  },
  "userused@": {
    "edit": False,
    "inherit": False,
    "values": "<size>",
    "type": "size"
  },
  "logicalreferenced": {
    "edit": False,
    "inherit": False,
    "values": "<size>",
    "type": "size"
  },
  "checksum": {
    "edit": True,
    "inherit": True,
    "values": "on | off | fletcher2 | fletcher4 | sha256 | sha512 | skein | edonr",
    "type": "bool"
  },
  "nbmand": {
    "edit": True,
    "inherit": True,
    "values": "on | off",
    "type": "bool"
  }
}


def _from_auto(name, value, source='auto'):
    '''
    some more complex patching for zfs.from_auto
    '''
    with patch.object(salt.utils.zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)), \
         patch.object(salt.utils.zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
        return salt.utils.zfs.from_auto(name, value, source)


def _from_auto_dict(values, source='auto'):
    '''
    some more complex patching for zfs.from_auto_dict
    '''
    with patch.object(salt.utils.zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)), \
         patch.object(salt.utils.zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
        return salt.utils.zfs.from_auto_dict(values, source)


def _to_auto(name, value, source='auto', convert_to_human=True):
    '''
    some more complex patching for zfs.to_auto
    '''
    with patch.object(salt.utils.zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)), \
         patch.object(salt.utils.zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
        return salt.utils.zfs.to_auto(name, value, source, convert_to_human)


def _to_auto_dict(values, source='auto', convert_to_human=True):
    '''
    some more complex patching for zfs.to_auto_dict
    '''
    with patch.object(salt.utils.zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)), \
         patch.object(salt.utils.zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
        return salt.utils.zfs.to_auto_dict(values, source, convert_to_human)


utils_patch = {
    'zfs.is_supported': MagicMock(return_value=True),
    'zfs.has_feature_flags': MagicMock(return_value=True),
    'zfs.property_data_zpool': MagicMock(return_value=pmap_zpool),
    'zfs.property_data_zfs': MagicMock(return_value=pmap_zfs),
    # NOTE: we make zpool_command and zfs_command a NOOP
    #       these are extensively tested in tests.unit.utils.test_zfs
    'zfs.zpool_command': MagicMock(return_value='/bin/false'),
    'zfs.zfs_command': MagicMock(return_value='/bin/false'),
    # NOTE: from_auto_dict is a special snowflake
    #       internally it calls multiple calls from
    #       salt.utils.zfs but we cannot patch those using
    #       the common methode, __utils__ is not available
    #       so they are direct calls, we do some voodoo here.
    'zfs.from_auto_dict': _from_auto_dict,
    'zfs.from_auto': _from_auto,
    'zfs.to_auto_dict': _to_auto_dict,
    'zfs.to_auto': _to_auto,
}


# Skip this test case if we don't have access to mock!
@skipIf(NO_MOCK, NO_MOCK_REASON)
class ZfsUtilsTestCase(TestCase):
    '''
    This class contains a set of functions that test salt.utils.zfs utils
    '''
    ## NOTE: test parameter parsing
    def test_is_supported(self):
        '''
        Test zfs.is_supported method
        '''
        for value in [False, True]:
            with patch('salt.utils.path.which',
                       MagicMock(return_value=value)):
                with patch('salt.utils.platform.is_linux',
                                  MagicMock(return_value=value)):
                    self.assertEqual(value, zfs.is_supported())

    def test_property_data_zpool(self):
        '''
        Test parsing of zpool get output
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, '_exec', MagicMock(return_value=pmap_exec_zpool)):
                    self.assertEqual(zfs.property_data_zpool(), pmap_zpool)

    def test_property_data_zfs(self):
        '''
        Test parsing of zfs get output
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, '_exec', MagicMock(return_value=pmap_exec_zfs)):
                    self.assertEqual(zfs.property_data_zfs(), pmap_zfs)

    ## NOTE: testing from_bool results
    def test_from_bool_on(self):
        '''
        Test from_bool with 'on'
        '''
        self.assertTrue(zfs.from_bool('on'))
        self.assertTrue(zfs.from_bool(zfs.from_bool('on')))

    def test_from_bool_off(self):
        '''
        Test from_bool with 'off'
        '''
        self.assertFalse(zfs.from_bool('off'))
        self.assertFalse(zfs.from_bool(zfs.from_bool('off')))

    def test_from_bool_none(self):
        '''
        Test from_bool with 'none'
        '''
        self.assertEqual(zfs.from_bool('none'), None)
        self.assertEqual(zfs.from_bool(zfs.from_bool('none')), None)

    def test_from_bool_passthrough(self):
        '''
        Test from_bool with 'passthrough'
        '''
        self.assertEqual(zfs.from_bool('passthrough'), 'passthrough')
        self.assertEqual(zfs.from_bool(zfs.from_bool('passthrough')), 'passthrough')

    def test_from_bool_alt_yes(self):
        '''
        Test from_bool_alt with 'yes'
        '''
        self.assertTrue(zfs.from_bool_alt('yes'))
        self.assertTrue(zfs.from_bool_alt(zfs.from_bool_alt('yes')))

    def test_from_bool_alt_no(self):
        '''
        Test from_bool_alt with 'no'
        '''
        self.assertFalse(zfs.from_bool_alt('no'))
        self.assertFalse(zfs.from_bool_alt(zfs.from_bool_alt('no')))

    def test_from_bool_alt_none(self):
        '''
        Test from_bool_alt with 'none'
        '''
        self.assertEqual(zfs.from_bool_alt('none'), None)
        self.assertEqual(zfs.from_bool_alt(zfs.from_bool_alt('none')), None)

    def test_from_bool_alt_passthrough(self):
        '''
        Test from_bool_alt with 'passthrough'
        '''
        self.assertEqual(zfs.from_bool_alt('passthrough'), 'passthrough')
        self.assertEqual(zfs.from_bool_alt(zfs.from_bool_alt('passthrough')), 'passthrough')

    ## NOTE: testing to_bool results
    def test_to_bool_true(self):
        '''
        Test to_bool with True
        '''
        self.assertEqual(zfs.to_bool(True), 'on')
        self.assertEqual(zfs.to_bool(zfs.to_bool(True)), 'on')

    def test_to_bool_false(self):
        '''
        Test to_bool with False
        '''
        self.assertEqual(zfs.to_bool(False), 'off')
        self.assertEqual(zfs.to_bool(zfs.to_bool(False)), 'off')

    def test_to_bool_none(self):
        '''
        Test to_bool with None
        '''
        self.assertEqual(zfs.to_bool(None), 'none')
        self.assertEqual(zfs.to_bool(zfs.to_bool(None)), 'none')

    def test_to_bool_passthrough(self):
        '''
        Test to_bool with 'passthrough'
        '''
        self.assertEqual(zfs.to_bool('passthrough'), 'passthrough')
        self.assertEqual(zfs.to_bool(zfs.to_bool('passthrough')), 'passthrough')

    def test_to_bool_alt_true(self):
        '''
        Test to_bool_alt with True
        '''
        self.assertEqual(zfs.to_bool_alt(True), 'yes')
        self.assertEqual(zfs.to_bool_alt(zfs.to_bool_alt(True)), 'yes')

    def test_to_bool_alt_false(self):
        '''
        Test to_bool_alt with False
        '''
        self.assertEqual(zfs.to_bool_alt(False), 'no')
        self.assertEqual(zfs.to_bool_alt(zfs.to_bool_alt(False)), 'no')

    def test_to_bool_alt_none(self):
        '''
        Test to_bool_alt with None
        '''
        self.assertEqual(zfs.to_bool_alt(None), 'none')
        self.assertEqual(zfs.to_bool_alt(zfs.to_bool_alt(None)), 'none')

    def test_to_bool_alt_passthrough(self):
        '''
        Test to_bool_alt with 'passthrough'
        '''
        self.assertEqual(zfs.to_bool_alt('passthrough'), 'passthrough')
        self.assertEqual(zfs.to_bool_alt(zfs.to_bool_alt('passthrough')), 'passthrough')

    ## NOTE: testing from_numeric results
    def test_from_numeric_str(self):
        '''
        Test from_numeric with '42'
        '''
        self.assertEqual(zfs.from_numeric('42'), 42)
        self.assertEqual(zfs.from_numeric(zfs.from_numeric('42')), 42)

    def test_from_numeric_int(self):
        '''
        Test from_numeric with 42
        '''
        self.assertEqual(zfs.from_numeric(42), 42)
        self.assertEqual(zfs.from_numeric(zfs.from_numeric(42)), 42)

    def test_from_numeric_none(self):
        '''
        Test from_numeric with 'none'
        '''
        self.assertEqual(zfs.from_numeric('none'), None)
        self.assertEqual(zfs.from_numeric(zfs.from_numeric('none')), None)

    def test_from_numeric_passthrough(self):
        '''
        Test from_numeric with 'passthrough'
        '''
        self.assertEqual(zfs.from_numeric('passthrough'), 'passthrough')
        self.assertEqual(zfs.from_numeric(zfs.from_numeric('passthrough')), 'passthrough')

    ## NOTE: testing to_numeric results
    def test_to_numeric_str(self):
        '''
        Test to_numeric with '42'
        '''
        self.assertEqual(zfs.to_numeric('42'), 42)
        self.assertEqual(zfs.to_numeric(zfs.to_numeric('42')), 42)

    def test_to_numeric_int(self):
        '''
        Test to_numeric with 42
        '''
        self.assertEqual(zfs.to_numeric(42), 42)
        self.assertEqual(zfs.to_numeric(zfs.to_numeric(42)), 42)

    def test_to_numeric_none(self):
        '''
        Test to_numeric with 'none'
        '''
        self.assertEqual(zfs.to_numeric(None), 'none')
        self.assertEqual(zfs.to_numeric(zfs.to_numeric(None)), 'none')

    def test_to_numeric_passthrough(self):
        '''
        Test to_numeric with 'passthrough'
        '''
        self.assertEqual(zfs.to_numeric('passthrough'), 'passthrough')
        self.assertEqual(zfs.to_numeric(zfs.to_numeric('passthrough')), 'passthrough')

    ## NOTE: testing from_size results
    def test_from_size_absolute(self):
        '''
        Test from_size with '5G'
        '''
        self.assertEqual(zfs.from_size('5G'), 5368709120)
        self.assertEqual(zfs.from_size(zfs.from_size('5G')), 5368709120)

    def test_from_size_decimal(self):
        '''
        Test from_size with '4.20M'
        '''
        self.assertEqual(zfs.from_size('4.20M'), 4404019)
        self.assertEqual(zfs.from_size(zfs.from_size('4.20M')), 4404019)

    def test_from_size_none(self):
        '''
        Test from_size with 'none'
        '''
        self.assertEqual(zfs.from_size('none'), None)
        self.assertEqual(zfs.from_size(zfs.from_size('none')), None)

    def test_from_size_passthrough(self):
        '''
        Test from_size with 'passthrough'
        '''
        self.assertEqual(zfs.from_size('passthrough'), 'passthrough')
        self.assertEqual(zfs.from_size(zfs.from_size('passthrough')), 'passthrough')

    ## NOTE: testing to_size results
    def test_to_size_str_absolute(self):
        '''
        Test to_size with '5368709120'
        '''
        self.assertEqual(zfs.to_size('5368709120'), '5G')
        self.assertEqual(zfs.to_size(zfs.to_size('5368709120')), '5G')

    def test_to_size_str_decimal(self):
        '''
        Test to_size with '4404019'
        '''
        self.assertEqual(zfs.to_size('4404019'), '4.20M')
        self.assertEqual(zfs.to_size(zfs.to_size('4404019')), '4.20M')

    def test_to_size_int_absolute(self):
        '''
        Test to_size with 5368709120
        '''
        self.assertEqual(zfs.to_size(5368709120), '5G')
        self.assertEqual(zfs.to_size(zfs.to_size(5368709120)), '5G')

    def test_to_size_int_decimal(self):
        '''
        Test to_size with 4404019
        '''
        self.assertEqual(zfs.to_size(4404019), '4.20M')
        self.assertEqual(zfs.to_size(zfs.to_size(4404019)), '4.20M')

    def test_to_size_none(self):
        '''
        Test to_size with 'none'
        '''
        self.assertEqual(zfs.to_size(None), 'none')
        self.assertEqual(zfs.to_size(zfs.to_size(None)), 'none')

    def test_to_size_passthrough(self):
        '''
        Test to_size with 'passthrough'
        '''
        self.assertEqual(zfs.to_size('passthrough'), 'passthrough')
        self.assertEqual(zfs.to_size(zfs.to_size('passthrough')), 'passthrough')

    ## NOTE: testing from_str results
    def test_from_str_space(self):
        '''
        Test from_str with "\"my pool/my dataset\"
        '''
        self.assertEqual(zfs.from_str('"my pool/my dataset"'), 'my pool/my dataset')
        self.assertEqual(zfs.from_str(zfs.from_str('"my pool/my dataset"')), 'my pool/my dataset')

    def test_from_str_squote_space(self):
        '''
        Test from_str with "my pool/jorge's dataset"
        '''
        self.assertEqual(zfs.from_str("my pool/jorge's dataset"), "my pool/jorge's dataset")
        self.assertEqual(zfs.from_str(zfs.from_str("my pool/jorge's dataset")), "my pool/jorge's dataset")

    def test_from_str_dquote_space(self):
        '''
        Test from_str with "my pool/the \"good\" stuff"
        '''
        self.assertEqual(zfs.from_str("my pool/the \"good\" stuff"), 'my pool/the "good" stuff')
        self.assertEqual(zfs.from_str(zfs.from_str("my pool/the \"good\" stuff")), 'my pool/the "good" stuff')

    def test_from_str_none(self):
        '''
        Test from_str with 'none'
        '''
        self.assertEqual(zfs.from_str('none'), None)
        self.assertEqual(zfs.from_str(zfs.from_str('none')), None)

    def test_from_str_passthrough(self):
        '''
        Test from_str with 'passthrough'
        '''
        self.assertEqual(zfs.from_str('passthrough'), 'passthrough')
        self.assertEqual(zfs.from_str(zfs.from_str('passthrough')), 'passthrough')

    ## NOTE: testing to_str results
    def test_to_str_space(self):
        '''
        Test to_str with 'my pool/my dataset'
        '''
        ## NOTE: for fun we use both the '"str"' and "\"str\"" way of getting the literal string: "str"
        self.assertEqual(zfs.to_str('my pool/my dataset'), '"my pool/my dataset"')
        self.assertEqual(zfs.to_str(zfs.to_str('my pool/my dataset')), "\"my pool/my dataset\"")

    def test_to_str_squote_space(self):
        '''
        Test to_str with "my pool/jorge's dataset"
        '''
        self.assertEqual(zfs.to_str("my pool/jorge's dataset"), "\"my pool/jorge's dataset\"")
        self.assertEqual(zfs.to_str(zfs.to_str("my pool/jorge's dataset")), "\"my pool/jorge's dataset\"")

    def test_to_str_none(self):
        '''
        Test to_str with 'none'
        '''
        self.assertEqual(zfs.to_str(None), 'none')
        self.assertEqual(zfs.to_str(zfs.to_str(None)), 'none')

    def test_to_str_passthrough(self):
        '''
        Test to_str with 'passthrough'
        '''
        self.assertEqual(zfs.to_str('passthrough'), 'passthrough')
        self.assertEqual(zfs.to_str(zfs.to_str('passthrough')), 'passthrough')

    ## NOTE: testing is_snapshot
    def test_is_snapshot_snapshot(self):
        '''
        Test is_snapshot with a valid snapshot name
        '''
        self.assertTrue(zfs.is_snapshot('zpool_name/dataset@backup'))

    def test_is_snapshot_bookmark(self):
        '''
        Test is_snapshot with a valid bookmark name
        '''
        self.assertFalse(zfs.is_snapshot('zpool_name/dataset#backup'))

    def test_is_snapshot_filesystem(self):
        '''
        Test is_snapshot with a valid filesystem name
        '''
        self.assertFalse(zfs.is_snapshot('zpool_name/dataset'))

    ## NOTE: testing is_bookmark
    def test_is_bookmark_snapshot(self):
        '''
        Test is_bookmark with a valid snapshot name
        '''
        self.assertFalse(zfs.is_bookmark('zpool_name/dataset@backup'))

    def test_is_bookmark_bookmark(self):
        '''
        Test is_bookmark with a valid bookmark name
        '''
        self.assertTrue(zfs.is_bookmark('zpool_name/dataset#backup'))

    def test_is_bookmark_filesystem(self):
        '''
        Test is_bookmark with a valid filesystem name
        '''
        self.assertFalse(zfs.is_bookmark('zpool_name/dataset'))

    ## NOTE: testing is_dataset
    def test_is_dataset_snapshot(self):
        '''
        Test is_dataset with a valid snapshot name
        '''
        self.assertFalse(zfs.is_dataset('zpool_name/dataset@backup'))

    def test_is_dataset_bookmark(self):
        '''
        Test is_dataset with a valid bookmark name
        '''
        self.assertFalse(zfs.is_dataset('zpool_name/dataset#backup'))

    def test_is_dataset_filesystem(self):
        '''
        Test is_dataset with a valid filesystem/volume name
        '''
        self.assertTrue(zfs.is_dataset('zpool_name/dataset'))

    ## NOTE: testing zfs_command
    def test_zfs_command_simple(self):
        '''
        Test if zfs_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        self.assertEqual(
                            zfs.zfs_command('list'),
                            "/sbin/zfs list"
                        )

    def test_zfs_command_none_target(self):
        '''
        Test if zfs_command builds the correct string with a target of None
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        self.assertEqual(
                            zfs.zfs_command('list', target=[None, 'mypool', None]),
                            "/sbin/zfs list mypool"
                        )

    def test_zfs_command_flag(self):
        '''
        Test if zfs_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        my_flags = [
                            '-r',  # recursive
                        ]
                        self.assertEqual(
                            zfs.zfs_command('list', flags=my_flags),
                            "/sbin/zfs list -r"
                        )

    def test_zfs_command_opt(self):
        '''
        Test if zfs_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        my_opts = {
                            '-t': 'snap',  # only list snapshots
                        }
                        self.assertEqual(
                            zfs.zfs_command('list', opts=my_opts),
                            "/sbin/zfs list -t snap"
                        )

    def test_zfs_command_flag_opt(self):
        '''
        Test if zfs_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        my_flags = [
                            '-r',  # recursive
                        ]
                        my_opts = {
                            '-t': 'snap',  # only list snapshots
                        }
                        self.assertEqual(
                            zfs.zfs_command('list', flags=my_flags, opts=my_opts),
                            "/sbin/zfs list -r -t snap"
                        )

    def test_zfs_command_target(self):
        '''
        Test if zfs_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        my_flags = [
                            '-r',  # recursive
                        ]
                        my_opts = {
                            '-t': 'snap',  # only list snapshots
                        }
                        self.assertEqual(
                            zfs.zfs_command('list', flags=my_flags, opts=my_opts, target='mypool'),
                            "/sbin/zfs list -r -t snap mypool"
                        )

    def test_zfs_command_target_with_space(self):
        '''
        Test if zfs_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        my_flags = [
                            '-r',  # recursive
                        ]
                        my_opts = {
                            '-t': 'snap',  # only list snapshots
                        }
                        self.assertEqual(
                            zfs.zfs_command('list', flags=my_flags, opts=my_opts, target='my pool'),
                            '/sbin/zfs list -r -t snap "my pool"'
                        )

    def test_zfs_command_property(self):
        '''
        Test if zfs_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        self.assertEqual(
                            zfs.zfs_command('get', property_name='quota', target='mypool'),
                            "/sbin/zfs get quota mypool"
                        )

    def test_zfs_command_property_value(self):
        '''
        Test if zfs_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        my_flags = [
                            '-r',  # recursive
                        ]
                        self.assertEqual(
                            zfs.zfs_command('set', flags=my_flags, property_name='quota', property_value='5G', target='mypool'),
                            "/sbin/zfs set -r quota=5368709120 mypool"
                        )

    def test_zfs_command_multi_property_value(self):
        '''
        Test if zfs_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        property_name = ['quota', 'readonly']
                        property_value = ['5G', 'no']
                        self.assertEqual(
                            zfs.zfs_command('set', property_name=property_name, property_value=property_value, target='mypool'),
                            "/sbin/zfs set quota=5368709120 readonly=off mypool"
                        )

    def test_zfs_command_fs_props(self):
        '''
        Test if zfs_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        my_flags = [
                            '-p',  # create parent
                        ]
                        my_props = {
                            'quota': '1G',
                            'compression': 'lz4',
                        }
                        self.assertEqual(
                            zfs.zfs_command('create', flags=my_flags, filesystem_properties=my_props, target='mypool/dataset'),
                            "/sbin/zfs create -p -o compression=lz4 -o quota=1073741824 mypool/dataset"
                        )

    def test_zfs_command_fs_props_with_space(self):
        '''
        Test if zfs_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        my_props = {
                            'quota': '4.2M',
                            'compression': 'lz4',
                        }
                        self.assertEqual(
                            zfs.zfs_command('create', filesystem_properties=my_props, target="my pool/jorge's dataset"),
                            '/sbin/zfs create -o compression=lz4 -o quota=4404019 "my pool/jorge\'s dataset"'
                        )

    ## NOTE: testing zpool_command
    def test_zpool_command_simple(self):
        '''
        Test if zfs_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        self.assertEqual(
                            zfs.zpool_command('list'),
                            "/sbin/zpool list"
                        )

    def test_zpool_command_opt(self):
        '''
        Test if zpool_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        my_opts = {
                            '-o': 'name,size',  # show only name and size
                        }
                        self.assertEqual(
                            zfs.zpool_command('list', opts=my_opts),
                            "/sbin/zpool list -o name,size"
                        )

    def test_zpool_command_opt_list(self):
        '''
        Test if zpool_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        my_opts = {
                            '-d': ['/tmp', '/zvol'],
                        }
                        self.assertEqual(
                            zfs.zpool_command('import', opts=my_opts, target='mypool'),
                            "/sbin/zpool import -d /tmp -d /zvol mypool"
                        )

    def test_zpool_command_flag_opt(self):
        '''
        Test if zpool_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        my_opts = {
                            '-o': 'name,size',  # show only name and size
                        }
                        self.assertEqual(
                            zfs.zpool_command('list', opts=my_opts),
                            "/sbin/zpool list -o name,size"
                        )

    def test_zpool_command_target(self):
        '''
        Test if zpool_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        my_opts = {
                            '-o': 'name,size',  # show only name and size
                        }
                        self.assertEqual(
                            zfs.zpool_command('list', opts=my_opts, target='mypool'),
                            "/sbin/zpool list -o name,size mypool"
                        )

    def test_zpool_command_target_with_space(self):
        '''
        Test if zpool_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        fs_props = {
                            'quota': '100G',
                        }
                        pool_props = {
                            'comment': "jorge's comment has a space",
                        }
                        self.assertEqual(
                            zfs.zpool_command('create', pool_properties=pool_props, filesystem_properties=fs_props, target='my pool'),
                            "/sbin/zpool create -O quota=107374182400 -o comment=\"jorge's comment has a space\" \"my pool\""
                        )

    def test_zpool_command_property(self):
        '''
        Test if zpool_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        self.assertEqual(
                            zfs.zpool_command('get', property_name='comment', target='mypool'),
                            "/sbin/zpool get comment mypool"
                        )

    def test_zpool_command_property_value(self):
        '''
        Test if zpool_command builds the correct string
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        my_flags = [
                            '-v',  # verbose
                        ]
                        self.assertEqual(
                            zfs.zpool_command('iostat', flags=my_flags, target=['mypool', 60, 1]),
                            "/sbin/zpool iostat -v mypool 60 1"
                        )

    def test_parse_command_result_success(self):
        '''
        Test if parse_command_result returns the expected result
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        res = {}
                        res['retcode'] = 0
                        res['stderr'] = ''
                        res['stdout'] = ''
                        self.assertEqual(
                            zfs.parse_command_result(res, 'tested'),
                            OrderedDict([('tested', True)]),
                        )

    def test_parse_command_result_success_nolabel(self):
        '''
        Test if parse_command_result returns the expected result
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        res = {}
                        res['retcode'] = 0
                        res['stderr'] = ''
                        res['stdout'] = ''
                        self.assertEqual(
                            zfs.parse_command_result(res),
                            OrderedDict(),
                        )

    def test_parse_command_result_fail(self):
        '''
        Test if parse_command_result returns the expected result on failure
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        res = {}
                        res['retcode'] = 1
                        res['stderr'] = ''
                        res['stdout'] = ''
                        self.assertEqual(
                            zfs.parse_command_result(res, 'tested'),
                            OrderedDict([('tested', False)]),
                        )

    def test_parse_command_result_nolabel(self):
        '''
        Test if parse_command_result returns the expected result on failure
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        res = {}
                        res['retcode'] = 1
                        res['stderr'] = ''
                        res['stdout'] = ''
                        self.assertEqual(
                            zfs.parse_command_result(res),
                            OrderedDict(),
                        )

    def test_parse_command_result_fail_message(self):
        '''
        Test if parse_command_result returns the expected result on failure with stderr
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        res = {}
                        res['retcode'] = 1
                        res['stderr'] = "\n".join([
                            'ice is not hot',
                            'usage:',
                            'this should not be printed',
                        ])
                        res['stdout'] = ''
                        self.assertEqual(
                            zfs.parse_command_result(res, 'tested'),
                            OrderedDict([('tested', False), ('error', 'ice is not hot')]),
                        )

    def test_parse_command_result_fail_message_nolabel(self):
        '''
        Test if parse_command_result returns the expected result on failure with stderr
        '''
        with patch.object(zfs, '_zfs_cmd', MagicMock(return_value='/sbin/zfs')):
            with patch.object(zfs, '_zpool_cmd', MagicMock(return_value='/sbin/zpool')):
                with patch.object(zfs, 'property_data_zfs', MagicMock(return_value=pmap_zfs)):
                    with patch.object(zfs, 'property_data_zpool', MagicMock(return_value=pmap_zpool)):
                        res = {}
                        res['retcode'] = 1
                        res['stderr'] = "\n".join([
                            'ice is not hot',
                            'usage:',
                            'this should not be printed',
                        ])
                        res['stdout'] = ''
                        self.assertEqual(
                            zfs.parse_command_result(res),
                            OrderedDict([('error', 'ice is not hot')]),
                        )

# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
