#!/usr/bin/python3

import inspect
import unittest

import re

from datetime import datetime
from difflib import unified_diff
from os import statvfs, uname, utime, environ, access, R_OK, W_OK
from os.path import exists, isdir, join
from pathlib import Path
from shutil import copy
from subprocess import call, CalledProcessError, run
from sys import argv, exit, modules
from time import mktime

from common_framework import (BaseTestCase, get_test_binaries, main, mkstring,
                              IPROMPT, KNOWNFAIL, UNSUPPORTED)

from func_cpu_trap_flag import cpu_trap_flag
from func_ds2_file_seek_tell import ds2_file_seek_tell
from func_ds2_file_seek_read import ds2_file_seek_read
from func_ds2_set_fattrs import ds2_set_fattrs
from func_ds3_file_access import ds3_file_access
from func_ds3_lock_concurrent import ds3_lock_concurrent
from func_ds3_lock_two_handles import ds3_lock_two_handles
from func_ds3_lock_readlckd import ds3_lock_readlckd
from func_ds3_lock_readonly import ds3_lock_readonly
from func_ds3_lock_twice import ds3_lock_twice
from func_ds3_lock_writable import ds3_lock_writable
from func_ds3_share_open_access import ds3_share_open_access
from func_ds3_share_open_twice import ds3_share_open_twice
from func_lfn_voln_info import lfn_voln_info
from func_lfs_disk_info import lfs_disk_info
from func_label_create import (label_create, label_create_on_lfns,
                                label_create_noduplicate, label_create_nonrootdir,
                                label_delete_wildcard, label_delete_recreate)
from func_lfs_file_info import lfs_file_info
from func_lfs_file_seek_tell import lfs_file_seek_tell
from func_libi86_testsuite import libi86_create_items
from func_memory_dpmi_dpmi10_ldt import memory_dpmi_dpmi10_ldt
from func_memory_dpmi_japheth import memory_dpmi_japheth
from func_memory_dpmi_leak_check import memory_dpmi_leak_check
from func_memory_dpmi_leak_check_dos import memory_dpmi_leak_check_dos
from func_memory_ems_borland import memory_ems_borland
from func_memory_hma import (memory_hma_freespace, memory_hma_alloc, memory_hma_a20,
                             memory_hma_alloc3, memory_hma_chain)
from func_memory_uma import memory_uma_strategy
from func_memory_xms import memory_xms
from func_mfs_findfile import mfs_findfile
from func_mfs_truename import mfs_truename
from func_network import network_pktdriver_mtcp
from func_pit_mode_2 import pit_mode_2

SYSTYPE_DRDOS_ENHANCED = "Enhanced DR-DOS"
SYSTYPE_DRDOS_ORIGINAL = "Original DR-DOS"
SYSTYPE_DRDOS_OLD = "Old DR-DOS"
SYSTYPE_PCDOS_NEW = "New PC-DOS"
SYSTYPE_PCDOS_OLD = "Old PC-DOS"
SYSTYPE_MSDOS_NEW = "New MS-DOS"
SYSTYPE_MSDOS_INTERMEDIATE = "Newer MS-DOS"
SYSTYPE_MSDOS_OLD = "Old MS-DOS"
SYSTYPE_MSDOS_NEC = "NEC MS-DOS"
SYSTYPE_FRDOS_OLD = "Old FreeDOS"
SYSTYPE_FRDOS_NEW = "FreeDOS"
SYSTYPE_FDPP = "FDPP"

PRGFIL_SFN = "PROGR~-I"
PRGFIL_LFN = "Program Files"


class OurTestCase(BaseTestCase):

    attrs = ['cputest', 'dpmitest', 'hmatest', 'nettest', 'umatest', 'xmstest',
             'labeltest']
    pname = "test_dos"

    # Tests using assembler

    def _test_mfs_directory_common(self, nametype, operation):
        if nametype == "LFN":
            ename = "mfslfn"
            testname = "test very long directory"
        elif nametype == "SFN":
            ename = "mfssfn"
            testname = "testdir"
        else:
            self.fail("Incorrect argument")

        testdir = self.mkworkdir('d')

        cwdnum = "0x0"

        if operation == "Create":
            ename += "dc"
            if nametype == "SFN":
                intnum = "0x3900"  # create
            else:
                intnum = "0x7139"
        elif operation in ["Delete", "DeleteNotEmpty"]:
            ename += "dd"
            if nametype == "SFN":
                intnum = "0x3a00"  # delete
            else:
                intnum = "0x713a"
            Path(testdir / testname).mkdir()
            if operation == "DeleteNotEmpty":
                self.mkfile("DirNotEm.pty", """hello\r\n""", join(testdir, testname))
        elif operation == "Chdir":
            ename += "dh"
            if nametype == "SFN":
                intnum = "0x3b00"  # chdir
                cwdnum = "0x4700"  # getcwd
            else:
                intnum = "0x713b"
                cwdnum = "0x7147"
            Path(testdir / testname).mkdir()
        else:
            self.fail("Incorrect argument")

        self.mkfile("testit.bat", """\
d:
c:\\%s
rem end
""" % ename, newline="\r\n")


        # compile sources
        self.mkcom_with_nasm(ename, r"""
bits 16
cpu 386

org 100h

section .text

    push    cs
    pop     ds

    mov     ax, %s
    mov     dx, dname
    int     21h

    jnc     prsucc

prfail:
    mov     dx, failmsg
    jmp     @1
prsucc:
    mov     dx, succmsg
@1:
    mov     ah, 9
    int     21h

    mov     ax, %s
    cmp     ax, 0x7147
    je      prcwd
    cmp     ax, 0x4700
    je      prcwd

exit:
    mov     ah, 4ch
    int     21h

prcwd:
; get cwd
    mov     dl, 0
    mov     si, curdir
    int     21h

    push    ds
    pop     es
    mov     di, si

    mov     cx, 128
    mov     al, 0
    cld
    repne   scasb
    mov     byte [di-1], ')'
    mov     byte [di], '$'

    mov     ah, 9
    mov     dx, pcurdir
    int     21h

    jmp     exit

section .data

dname:
    db  "%s",0

succmsg:
    db  "Directory Operation Success",13,10,'$'
failmsg:
    db  "Directory Operation Failed",13,10,'$'

pcurdir:
    db '('
curdir:
    times 128 db '$'

""" % (intnum, cwdnum, testname))

        results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
""")

        # test to see if the directory intnum made it through to linux
        if operation == "Create":
            self.assertIn("Directory Operation Success", results)
            self.assertTrue(isdir(join(testdir, testname)), "Directory not created")
        elif operation == "Delete":
            self.assertIn("Directory Operation Success", results)
            self.assertFalse(isdir(join(testdir, testname)), "Directory not deleted")
        elif operation == "DeleteNotEmpty":
            self.assertIn("Directory Operation Failed", results)
            self.assertTrue(isdir(join(testdir, testname)), "Directory incorrectly deleted")
        elif operation == "Chdir":
            self.assertIn("Directory Operation Success", results)
            if nametype == "SFN":
                self.assertIn("(" + testname.upper() + ")", results)
            else:
                self.assertIn("(" + testname + ")", results)

    def test_mfs_sfn_directory_create(self):
        """MFS SFN directory create"""
        self._test_mfs_directory_common("SFN", "Create")

    def test_mfs_sfn_directory_delete(self):
        """MFS SFN directory delete"""
        self._test_mfs_directory_common("SFN", "Delete")

    def test_mfs_sfn_directory_delete_not_empty(self):
        """MFS SFN directory delete not empty"""
        self._test_mfs_directory_common("SFN", "DeleteNotEmpty")

    def test_mfs_sfn_directory_chdir(self):
        """MFS SFN directory change current"""
        self._test_mfs_directory_common("SFN", "Chdir")

    def test_mfs_lfn_directory_create(self):
        """MFS LFN directory create"""
        self._test_mfs_directory_common("LFN", "Create")

    def test_mfs_lfn_directory_delete(self):
        """MFS LFN directory delete"""
        self._test_mfs_directory_common("LFN", "Delete")

    def test_mfs_lfn_directory_delete_not_empty(self):
        """MFS LFN directory delete not empty"""
        self._test_mfs_directory_common("LFN", "DeleteNotEmpty")

    def test_mfs_lfn_directory_chdir(self):
        """MFS LFN directory change current"""
        self._test_mfs_directory_common("LFN", "Chdir")

    def _test_mfs_get_current_directory(self, nametype):
        if nametype == "LFN":
            ename = "mfslfngd"
            testname = PRGFIL_LFN
        elif nametype == "SFN":
            ename = "mfssfngd"
            testname = PRGFIL_SFN
        else:
            self.fail("Incorrect argument")

        testdir = self.mkworkdir('d')

        if nametype == "SFN":
            cwdnum = "0x4700"  # getcwd
        else:
            cwdnum = "0x7147"

        Path(testdir / PRGFIL_LFN).mkdir()

        self.mkfile("testit.bat", """\
d:
cd %s
c:\\%s
rem end
""" % (PRGFIL_SFN, ename), newline="\r\n")

        # compile sources
        self.mkcom_with_nasm(ename, r"""
bits 16
cpu 386

org 100h

section .text

    push    cs
    pop     ds

; get cwd
    mov     ax, %s
    mov     dl, 0
    mov     si, curdir
    int     21h

    push    ds
    pop     es
    mov     di, si

    mov     cx, 128
    mov     al, 0
    cld
    repne   scasb
    mov     byte [di-1], ')'
    mov     byte [di], '$'

    mov     ah, 9
    mov     dx, pcurdir
    int     21h

exit:
    mov     ah, 4ch
    int     21h

section .data

pcurdir:
    db '('
curdir:
    times 128 db '$'

""" % cwdnum)

        results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
""")

        if nametype == "SFN":
            self.assertIn("(" + testname.upper() + ")", results)
        else:
            self.assertIn("(" + testname + ")", results)

    def test_mfs_sfn_get_current_directory(self):
        """MFS SFN get current directory"""
        self._test_mfs_get_current_directory("SFN")

    def test_mfs_lfn_get_current_directory(self):
        """MFS LFN get current directory"""
        self._test_mfs_get_current_directory("LFN")

    def _test_lfn_support(self, fstype, confsw):
        ename = "lfnsuppt"
        testdir = self.mkworkdir('d')

        if fstype == "MFS":
            config = """\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
"""
        else:       # FAT
            config = """\
$_hdimage = "dXXXXs/c:hdtype1 %s +1"
$_floppy_a = ""
""" % self.mkimage("12", cwd=testdir)

        config += """$_lfn_support = (%s)\n""" % confsw

        self.mkfile("testit.bat", """\
d:
c:\\%s
rem end
""" % ename, newline="\r\n")

        # compile sources

        self.mkcom_with_nasm(ename, r"""
bits 16
cpu 386

org 100h

section .text

    push    cs
    pop     ds

; Get current drive and store its letter in fspath
    mov     ax, 1900h
    int     21h
    add     al, 'A'
    mov     byte [fspath], al

; Get Volume info
;    Windows95 - LONG FILENAME - GET VOLUME INFORMATION
;
;    Call:
;      AX = 71A0h
;      DS:DX -> ASCIZ root name (e.g. "C:\")
;      ES:DI -> buffer for file system name
;      CX = size of ES:DI buffer
;
;    Return:
;      CF clear if successful
;        AX destroyed (0000h and 0200h seen)
;        BX = file system flags (see ;01783)
;        CX = maximum length of file name [usually 255]
;        DX = maximum length of path [usually 260]
;        ES:DI buffer filled (ASCIZ, e.g. "FAT","NTFS","CDFS")
;
;      CF set on error
;        AX = error code
;          7100h if function not supported

    mov     ax, 71a0h
    mov     dx, fspath ; ds:dx
    mov     di, fstype ; es:di
    mov     cx, fstypelen
    stc
    int     21h

    jc      chkfail

    cmp     byte [fstype], '$'
    je      prnofstype

prsuccess:
    mov     di, fstype
    mov     cx, fstypelen
    mov     al, 0
    cld
    repne   scasb
    mov     byte [di-1], ')'
    mov     byte [di], 13
    mov     byte [di+1], 10
    mov     byte [di+2], '$'
    mov     dx, success
    jmp     exit

prnofstype:
    mov     dx, nofstype
    jmp     exit

prnotsupported:
    mov     dx, notsupported
    jmp     exit

prcarryset:
    mov     dx, carryset
    jmp     exit

chkfail:
    cmp     ax, 7100h
    jne     prcarryset

    jmp     prnotsupported

exit:
    mov     ah, 9
    int     21h

    mov     ah, 4ch
    int 21h

section .data

carryset:
    db  "Carry Set",13,10,'$'
notsupported:
    db  "Not Supported(AX=0x7100)",13,10,'$'
nofstype:
    db  "Carry Not Set But No Filesystem Type",13,10,'$'
success:
    db  "Operation Success("
fstype:
    times 32 db '$'
fstypelen equ $ - fstype
successend:
    times 4 db 0
fspath:
    db  "?:\", 0
""")

        results = self.runDosemu("testit.bat", config=config)

        if fstype == "MFS":
            if confsw == "on":
                self.assertIn("Operation Success(%s)" % fstype, results)
            else:
                self.assertIn("Not Supported(AX=0x7100)", results)
        else:    # FAT
                self.assertIn("Not Supported(AX=0x7100)", results)

    def test_lfn_mfs_support_on(self):
        """LFN MFS Support On"""
        self._test_lfn_support("MFS", "on")

    def test_lfn_fat_support_on(self):
        """LFN FAT Support On"""
        self._test_lfn_support("FAT", "on")

    def test_lfn_mfs_support_off(self):
        """LFN MFS Support Off"""
        self._test_lfn_support("MFS", "off")

    def test_lfn_fat_support_off(self):
        """LFN FAT Support Off"""
        self._test_lfn_support("FAT", "off")

    def _test_fcb_read(self, fstype):
        testdir = self.mkworkdir('d')

        if fstype == "MFS":
            ename = "mfsfcbrd"
            fcbreadconfig = """\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
"""
        else:       # FAT
            ename = "fatfcbrd"
            fcbreadconfig = """\
$_hdimage = "dXXXXs/c:hdtype1 %s +1"
$_floppy_a = ""
""" % self.mkimage("12", cwd=testdir)

        testdata = mkstring(32)

        self.mkfile("testit.bat", """\
d:
echo %s > test.fil
c:\\%s
DIR
rem end
""" % (testdata, ename), newline="\r\n")

        # compile sources
        self.mkcom_with_nasm(ename, r"""
bits 16
cpu 386

org 100h

section .text

    push    cs
    pop     ds

    mov     ax, 0f00h			; open file
    mov     dx, fcb
    int     21h
    cmp     al, 0
    jne     prfailopen

    mov     ax, 1400h			; read from file
    mov     dx, fcb
    int     21h
    cmp     al, 3               ; partial read
    jne     prfailread

    jmp     prsucc

prfailopen:
    mov     dx, failopen
    jmp     @1

prfailread:
    mov     ax, 1000h			; close file
    mov     dx, fcb
    int     21h
    mov     dx, failread
    jmp     @1

prsucc:
    mov     dx, succstart
    mov     ah, 9
    int     21h

    mov     ax, 2f00h			; get DTA address in ES:BX
    int     21h

    mov     byte [es:bx+%d], '$'; terminate
    push    es
    pop     ds
    mov     dx, bx
    mov     ah, 9
    int     21h

    mov     ax, 1000h			; close file
    mov     dx, fcb
    int     21h

    push    cs
    pop     ds
    mov     dx, succend

@1:
    mov     ah, 9
    int     21h

exit:
    mov     ah, 4ch
    int     21h

section .data

fcb:
    db  0          ; 0 default drive
fn1:
    db  "% -8s"    ; 8 bytes
fe1:
    db  "% -3s"    ; 3 bytes
wk1:
    times 24 db 0

succstart:
    db  "Operation Success($"
succend:
    db  ')',13,10,'$'
failopen:
    db  "Open Operation Failed",13,10,'$'
failread:
    db  "Read Operation Failed",13,10,'$'

""" % (len(testdata), "test", "fil"))

        results = self.runDosemu("testit.bat", config=fcbreadconfig)

        self.assertNotIn("Operation Failed", results)
        self.assertIn("Operation Success(%s)" % testdata, results)

    def test_fat_fcb_read(self):
        """FAT FCB file read simple"""
        self._test_fcb_read("FAT")

    def test_mfs_fcb_read(self):
        """MFS FCB file read simple"""
        self._test_fcb_read("MFS")

    def _test_fcb_read_alt_dta(self, fstype):
        testdir = self.mkworkdir('d')

        ename = "fcbradta"

        if fstype == "MFS":
            config = """\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
"""
        else:       # FAT
            config = """\
$_hdimage = "dXXXXs/c:hdtype1 %s +1"
$_floppy_a = ""
""" % self.mkimage("12", cwd=testdir)

        testdata = mkstring(32)

        self.mkfile("testit.bat", """\
d:
echo %s > test.fil
c:\\%s
DIR
rem end
""" % (testdata, ename), newline="\r\n")

        # compile sources
        self.mkcom_with_nasm(ename, r"""
bits 16
cpu 386

org 100h

section .text

    push    cs
    pop     ds

    mov     ax, 1a00h			; set DTA
    mov     dx, altdta
    int     21h

    mov     ax, 2f00h			; get DTA address in ES:BX
    int     21h
    mov     ax, cs
    mov     dx, es
    cmp     dx, ax
    jne     prfaildtaset
    cmp     bx, altdta
    jne     prfaildtaset

    mov     ax, 0f00h			; open file
    mov     dx, fcb
    int     21h
    cmp     al, 0
    jne     prfailopen

    mov     ax, 1400h			; read from file
    mov     dx, fcb
    int     21h
    cmp     al, 3				; partial read
    jne     prfailread

    jmp     prsucc

prfaildtaset:
    mov     dx, faildtaset
    jmp     @1

prfailopen:
    mov     dx, failopen
    jmp     @1

prfailread:
    mov     ax, 1000h			; close file
    mov     dx, fcb
    int     21h
    mov     dx, failread
    jmp     @1

prsucc:
    mov     dx, succstart
    mov     ah, 9
    int     21h

    mov     ax, 2f00h			; get DTA address in ES:BX
    int     21h

    mov     byte [es:bx+%d], '$'; terminate
    push    es
    pop     ds
    mov     dx, bx
    mov     ah, 9
    int     21h

    mov     ax, 1000h			; close file
    mov     dx, fcb
    int     21h

    push    cs
    pop     ds
    mov     dx, succend

@1:
    mov     ah, 9
    int     21h

exit:
    mov     ah, 4ch
    int     21h

section .data

fcb:
    db  0          ; 0 default drive
fn1:
    db  "% -8s"    ; 8 bytes
fe1:
    db  "% -3s"    ; 3 bytes
wk1:
    times 24 db 0

succstart:
    db  "Operation Success($"
succend:
    db  ')',13,10,'$'
faildtaset:
    db  "Set DTA Operation Failed",13,10,'$'
failopen:
    db  "Open Operation Failed",13,10,'$'
failread:
    db  "Read Operation Failed",13,10,'$'

altdta:
    times 128 db 0

""" % (len(testdata), "test", "fil"))

        results = self.runDosemu("testit.bat", config=config)

        self.assertNotIn("Operation Failed", results)
        self.assertIn("Operation Success(%s)" % testdata, results)

    def test_fat_fcb_read_alt_dta(self):
        """FAT FCB file read alternate DTA"""
        self._test_fcb_read_alt_dta("FAT")

    def test_mfs_fcb_read_alt_dta(self):
        """MFS FCB file read alternate DTA"""
        self._test_fcb_read_alt_dta("MFS")

    def test_fat_label_create_simple(self):
        """FAT FCB label create simple"""
        label_create(self, "FAT", None)
    test_fat_label_create_simple.labeltest = True

    def test_fat_label_create_bpb12(self):
        """FAT FCB label create BPB FAT12"""
        label_create(self, "FAT", 'bpb12')
    test_fat_label_create_bpb12.labeltest = True

    def test_fat_label_create_bpb16(self):
        """FAT FCB label create BPB FAT16"""
        label_create(self, "FAT", 'bpb16')
    test_fat_label_create_bpb16.labeltest = True

    def test_fat_label_create_bpb32(self):
        """FAT FCB label create BPB FAT32"""
        label_create(self, "FAT", 'bpb32')
    test_fat_label_create_bpb32.labeltest = True

    def test_fat_label_create_prefile(self):
        """FAT FCB label create file beforehand"""
        label_create(self, "FAT", 'prefile')
    test_fat_label_create_prefile.labeltest = True

    def test_fat_label_create_predir(self):
        """FAT FCB label create directory beforehand"""
        label_create(self, "FAT", 'predir')
    test_fat_label_create_predir.labeltest = True

    def test_fat_label_create_postfile(self):
        """FAT FCB label create file afterwards"""
        label_create(self, "FAT", 'postfile')
    test_fat_label_create_postfile.labeltest = True

    def test_fat_label_create_postdir(self):
        """FAT FCB label create directory afterwards"""
        label_create(self, "FAT", 'postdir')
    test_fat_label_create_postdir.labeltest = True

    def test_fat_label_create_on_lfns(self):
        """FAT FCB label create on top of LFNs"""
        label_create_on_lfns(self)
    test_fat_label_create_on_lfns.labeltest = True

    def test_fat_label_create_noduplicate(self):
        """FAT FCB label create no duplicate"""
        label_create_noduplicate(self, "FAT")
    test_fat_label_create_noduplicate.labeltest = True

    def _test_fcb_write(self, fstype):
        testdir = self.mkworkdir('d')

        if fstype == "MFS":
            ename = "mfsfcbwr"
            fcbreadconfig = """\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
"""
        else:       # FAT
            ename = "fatfcbwr"
            fcbreadconfig = """\
$_hdimage = "dXXXXs/c:hdtype1 %s +1"
$_floppy_a = ""
""" % self.mkimage("12", cwd=testdir)

        testdata = mkstring(32)

        self.mkfile("testit.bat", """\
d:
c:\\%s
DIR
type test.fil
rem end
""" % ename, newline="\r\n")

        # compile sources
        self.mkcom_with_nasm(ename, r"""
bits 16
cpu 386

org 100h

section .text

    push    cs
    pop     ds

    mov     ax, 1600h           ; create file
    mov     dx, fcb
    int     21h
    cmp     al, 0
    jne     prfailopen

    mov     si, data            ; copy data to DTA
    mov     ax, 2f00h           ; get DTA address in ES:BX
    int     21h
    mov     di, bx
    mov     cx, datalen
    cld
    repnz movsb

    mov     ax, 1500h           ; write to file
    mov     dx, fcb
    mov     word [flrs], datalen; only the significant part
    int     21h
    cmp     al , 0
    jne     prfailwrite

    mov     dx, donewrite
    jmp     @2

prfailwrite:
    mov     dx, failwrite
    jmp     @2

prfailopen:
    mov     dx, failopen
    jmp     @1

@2:
    mov     ax, 1000h           ; close file
    push    dx
    mov     dx, fcb
    int     21h
    pop     dx

@1:
    mov     ah, 9
    int     21h

exit:
    mov     ah, 4ch
    int     21h

section .data

data:
    db  "Operation Success(%s)",13,10,'$'
datalen equ $ - data - 1

fcb:
    db  0          ; 0 default drive
fn1:
    db  "% -8s"    ; 8 bytes
fe1:
    db  "% -3s"    ; 3 bytes
fcbn:
    dw  0
flrs:
    dw  0
ffsz:
    dd  0
fdlw:
    dw  0
ftlw:
    dw  0
res8:
    times 8 db 0
fcbr:
    db  0
frrn:
    dd  0

failopen:
    db  "Open Operation Failed",13,10,'$'
failwrite:
    db  "Write Operation Failed",13,10,'$'
donewrite:
    db  "Write Operation Done",13,10,'$'

""" % (testdata, "test", "fil"))

        results = self.runDosemu("testit.bat", config=fcbreadconfig)

        self.assertNotIn("Operation Failed", results)
        self.assertIn("Operation Success(%s)" % testdata, results)

    def test_fat_fcb_write(self):
        """FAT FCB file write simple"""
        self._test_fcb_write("FAT")

    def test_mfs_fcb_write(self):
        """MFS FCB file write simple"""
        self._test_fcb_write("MFS")

    def _test_fcb_rename_common(self, fstype, testname):
        testdir = self.mkworkdir('d')

        if testname == "simple":
            ename = "mfsfcbr1"
            fn1 = "testa"
            fe1 = "bat"
            fn2 = "testb"
            fe2 = "bal"
            self.mkfile(fn1 + "." + fe1, """hello\r\n""", testdir)
        elif testname == "source_missing":
            ename = "mfsfcbr2"
            fn1 = "testa"
            fe1 = "bat"
            fn2 = "testb"
            fe2 = "bal"
        elif testname == "target_exists":
            ename = "mfsfcbr3"
            fn1 = "testa"
            fe1 = "bat"
            fn2 = "testb"
            fe2 = "bal"
            self.mkfile(fn1 + "." + fe1, """hello\r\n""", testdir)
            self.mkfile(fn2 + "." + fe2, """hello\r\n""", testdir)
        elif testname == "wild_one":
            ename = "mfsfcbr4"
            fn1 = "*"
            fe1 = "in"
            fn2 = "*"
            fe2 = "out"
            for f in ["one.in", "two.in", "three.in", "four.in", "five.in",
                      "none.ctl"]:
                self.mkfile(f, """hello\r\n""", testdir)
        elif testname == "wild_two":
            ename = "mfsfcbr5"
            fn1 = "a*"
            fe1 = "*"
            fn2 = "b*"
            fe2 = "out"
            for f in ["aone.in", "atwo.in", "athree.in", "afour.in",
                      "afive.in", "xnone.ctl"]:
                self.mkfile(f, """hello\r\n""", testdir)
        elif testname == "wild_three":
            # To rename "abc001.txt ... abc099.txt" to "abc601.txt....abc699.txt"
            # REN abc0??.txt ???6*.*
            ename = "mfsfcbr6"
            fn1 = "abc0??"
            fe1 = "*"
            fn2 = "???6*"
            fe2 = "*"
            for f in ["abc001.txt", "abc002.txt", "abc003.txt", "abc004.txt",
                      "abc005.txt", "abc010.txt", "xbc007.txt"]:
                self.mkfile(f, """hello\r\n""", testdir)
        elif testname == "wild_four":
            # To rename abc001.htm to abc001.ht
            # REN abc*.htm *.??
            ename = "mfsfcbr7"
            fn1 = "abc*"
            fe1 = "htm"
            fn2 = "*"
            fe2 = "??"
            for f in ["abc001.htm", "abc002.htm", "abc003.htm", "abc004.htm",
                      "abc005.htm", "abc010.htm", "xbc007.htm"]:
                self.mkfile(f, """hello\r\n""", testdir)

        self.mkfile("testit.bat", """\
d:
c:\\%s
DIR
rem end
""" % ename, newline="\r\n")

        # compile sources
        self.mkcom_with_nasm(ename, r"""
bits 16
cpu 386

org 100h

section .text

    push    cs
    pop     ds

    mov     ax, 1700h
    mov     dx, fcb
    int     21h

    cmp     al, 0
    je      prsucc

prfail:
    mov     dx, failmsg
    jmp     @1
prsucc:
    mov     dx, succmsg
@1:
    mov     ah, 9
    int     21h

exit:
    mov     ah, 4ch
    int     21h

section .data

fcb:
    db  0          ; 0 default drive
fn1:
    db  "% -8s"    ; 8 bytes
fe1:
    db  "% -3s"    ; 3 bytes
wk1:
    times 5 db 0
fn2:
    db  "% -8s"    ; 8 bytes
fe2:
    db  "% -3s"    ; 3 bytes
wk2:
    times 16 db 0

succmsg:
    db  "Rename Operation Success",13,10,'$'
failmsg:
    db  "Rename Operation Failed",13,10,'$'

""" % (fn1, fe1, fn2, fe2))

        def assertIsPresent(testdir, results, fstype, f, e, msg=None):
            if fstype == "MFS":
                self.assertTrue(exists(join(testdir, f + "." + e)), msg)
            else:
                self.assertRegex(results.upper(), r"%s( +|\.)%s" % (f.upper(), e.upper()))

        if fstype == "MFS":
            results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
""")
        else:       # FAT
            name = self.mkimage("12", cwd=testdir)
            results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 %s +1"
$_floppy_a = ""
""" % name)

        if testname == "simple":
            self.assertIn("Rename Operation Success", results)
            assertIsPresent(testdir, results, fstype, fn2, fe2, "File not renamed")

        elif testname == "source_missing":
            self.assertIn("Rename Operation Failed", results)

        elif testname == "target_exists":
            self.assertIn("Rename Operation Failed", results)

        elif testname == "wild_one":
            self.assertIn("Rename Operation Success", results)
            assertIsPresent(testdir, results, fstype, "one", "out", "File not renamed")
            assertIsPresent(testdir, results, fstype, "two", "out", "File not renamed")
            assertIsPresent(testdir, results, fstype, "three", "out", "File not renamed")
            assertIsPresent(testdir, results, fstype, "four", "out", "File not renamed")
            assertIsPresent(testdir, results, fstype, "five", "out", "File not renamed")
            assertIsPresent(testdir, results, fstype, "none", "ctl", "File incorrectly renamed")

        elif testname == "wild_two":
            self.assertIn("Rename Operation Success", results)
            assertIsPresent(testdir, results, fstype, "bone", "out", "File not renamed")
            assertIsPresent(testdir, results, fstype, "btwo", "out", "File not renamed")
            assertIsPresent(testdir, results, fstype, "bthree", "out", "File not renamed")
            assertIsPresent(testdir, results, fstype, "bfour", "out", "File not renamed")
            assertIsPresent(testdir, results, fstype, "bfive", "out", "File not renamed")
            assertIsPresent(testdir, results, fstype, "xnone", "ctl", "File incorrectly renamed")

        elif testname == "wild_three":
            self.assertIn("Rename Operation Success", results)
            assertIsPresent(testdir, results, fstype, "abc601", "txt", "File not renamed")
            assertIsPresent(testdir, results, fstype, "abc602", "txt", "File not renamed")
            assertIsPresent(testdir, results, fstype, "abc603", "txt", "File not renamed")
            assertIsPresent(testdir, results, fstype, "abc604", "txt", "File not renamed")
            assertIsPresent(testdir, results, fstype, "abc605", "txt", "File not renamed")
            assertIsPresent(testdir, results, fstype, "abc610", "txt", "File not renamed")
            assertIsPresent(testdir, results, fstype, "xbc007", "txt", "File incorrectly renamed")

        elif testname == "wild_four":
            self.assertIn("Rename Operation Success", results)
            assertIsPresent(testdir, results, fstype, "abc001", "ht", "File not renamed")
            assertIsPresent(testdir, results, fstype, "abc002", "ht", "File not renamed")
            assertIsPresent(testdir, results, fstype, "abc003", "ht", "File not renamed")
            assertIsPresent(testdir, results, fstype, "abc004", "ht", "File not renamed")
            assertIsPresent(testdir, results, fstype, "abc005", "ht", "File not renamed")
            assertIsPresent(testdir, results, fstype, "abc010", "ht", "File not renamed")
            assertIsPresent(testdir, results, fstype, "xbc007", "htm", "File incorrectly renamed")

    def test_fat_fcb_rename_simple(self):
        """FAT FCB file rename simple"""
        self._test_fcb_rename_common("FAT", "simple")

    def test_mfs_fcb_rename_simple(self):
        """MFS FCB file rename simple"""
        self._test_fcb_rename_common("MFS", "simple")

    def test_fat_fcb_rename_source_missing(self):
        """FAT FCB file rename source missing"""
        self._test_fcb_rename_common("FAT", "source_missing")

    def test_mfs_fcb_rename_source_missing(self):
        """MFS FCB file rename source missing"""
        self._test_fcb_rename_common("MFS", "source_missing")

    def test_fat_fcb_rename_target_exists(self):
        """FAT FCB file rename target exists"""
        self._test_fcb_rename_common("FAT", "target_exists")

    def test_mfs_fcb_rename_target_exists(self):
        """MFS FCB file rename target exists"""
        self._test_fcb_rename_common("MFS", "target_exists")

    def test_fat_fcb_rename_wild_1(self):
        """FAT FCB file rename wildcard one"""
        self._test_fcb_rename_common("FAT", "wild_one")

    def test_mfs_fcb_rename_wild_1(self):
        """MFS FCB file rename wildcard one"""
        self._test_fcb_rename_common("MFS", "wild_one")

    def test_fat_fcb_rename_wild_2(self):
        """FAT FCB file rename wildcard two"""
        self._test_fcb_rename_common("FAT", "wild_two")

    def test_mfs_fcb_rename_wild_2(self):
        """MFS FCB file rename wildcard two"""
        self._test_fcb_rename_common("MFS", "wild_two")

    def test_fat_fcb_rename_wild_3(self):
        """FAT FCB file rename wildcard three"""
        self._test_fcb_rename_common("FAT", "wild_three")

    def test_mfs_fcb_rename_wild_3(self):
        """MFS FCB file rename wildcard three"""
        self._test_fcb_rename_common("MFS", "wild_three")

    def test_fat_fcb_rename_wild_4(self):
        """FAT FCB file rename wildcard four"""
        self._test_fcb_rename_common("FAT", "wild_four")

    def test_mfs_fcb_rename_wild_4(self):
        """MFS FCB file rename wildcard four"""
        self._test_fcb_rename_common("MFS", "wild_four")

    def _test_fcb_delete_common(self, fstype, testname):
        testdir = self.mkworkdir('d')

        if testname == "simple":
            ename = "fcbdel1"
            fn1 = "testa"
            fe1 = "bat"
            self.mkfile(fn1 + "." + fe1, """hello\r\n""", testdir)
        elif testname == "missing":
            ename = "fcbdel2"
            fn1 = "testa"
            fe1 = "bat"
        elif testname == "wild_one":
            ename = "fcbdel3"
            fn1 = "*"
            fe1 = "in"
            for f in ["one.in", "two.in", "three.in", "four.in", "five.in",
                      "none.ctl"]:
                self.mkfile(f, """hello\r\n""", testdir)
        elif testname == "wild_two":
            ename = "fcbdel4"
            fn1 = "a*"
            fe1 = "*"
            for f in ["aone.in", "atwo.in", "athree.in", "afour.in",
                      "afive.in", "xnone.ctl"]:
                self.mkfile(f, """hello\r\n""", testdir)
        elif testname == "wild_three":
            # To delete "abc001.txt ... abc099.txt"
            ename = "fcbdel5"
            fn1 = "abc0??"
            fe1 = "*"
            for f in ["abc001.txt", "abc002.txt", "abc003.txt", "abc004.txt",
                      "abc005.txt", "abc010.txt", "xbc007.txt"]:
                self.mkfile(f, """hello\r\n""", testdir)

        self.mkfile("testit.bat", """\
d:
c:\\%s
DIR
rem end
""" % ename, newline="\r\n")

        # compile sources
        self.mkcom_with_nasm(ename, r"""
bits 16
cpu 386

org 100h

section .text

    push    cs
    pop     ds

    mov     ax, 1300h
    mov     dx, fcb
    int     21h

    cmp     al, 0
    je      prsucc

prfail:
    mov     dx, failmsg
    jmp     @1
prsucc:
    mov     dx, succmsg
@1:
    mov     ah, 9
    int     21h

exit:
    mov     ah, 4ch
    int     21h

section .data

fcb:
    db  0       ; 0 default drive
fn1:
    db  "% -8s"    ; 8 bytes
fe1:
    db  "% -3s"    ; 3 bytes
wk1:
    times 25 db 0

succmsg:
    db  "Delete Operation Success",13,10,'$'
failmsg:
    db  "Delete Operation Failed",13,10,'$'

""" % (fn1, fe1))

        def assertIsPresent(testdir, results, fstype, f, e, msg=None):
            if fstype == "MFS":
                self.assertTrue(exists(join(testdir, f + "." + e)), msg)
            else:
                self.assertRegex(results.upper(), r"%s( +|\.)%s" % (f.upper(), e.upper()))

        def assertIsNotPresent(testdir, results, fstype, f, e, msg=None):
            if fstype == "MFS":
                self.assertFalse(exists(join(testdir, f + "." + e)), msg)
            else:
                self.assertNotRegex(results.upper(), r"%s( +|\.)%s" % (f.upper(), e.upper()))

        if fstype == "MFS":
            results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
""")
        else:       # FAT
            name = self.mkimage("12", cwd=testdir)
            results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 %s +1"
$_floppy_a = ""
""" % name)

        if testname == "simple":
            self.assertIn("Delete Operation Success", results)
            assertIsNotPresent(testdir, results, fstype, fn1, fe1, "File not deleted")

        elif testname == "missing":
            self.assertIn("Delete Operation Failed", results)

        elif testname == "wild_one":
            self.assertIn("Delete Operation Success", results)
            assertIsNotPresent(testdir, results, fstype, "one", "in", "File not deleted")
            assertIsNotPresent(testdir, results, fstype, "two", "in", "File not deleted")
            assertIsNotPresent(testdir, results, fstype, "three", "in", "File not deleted")
            assertIsNotPresent(testdir, results, fstype, "four", "in", "File not deleted")
            assertIsNotPresent(testdir, results, fstype, "five", "in", "File not deleted")
            assertIsPresent(testdir, results, fstype, "none", "ctl", "File incorrectly deleted")

        elif testname == "wild_two":
            self.assertIn("Delete Operation Success", results)
            assertIsNotPresent(testdir, results, fstype, "aone", "in", "File not deleted")
            assertIsNotPresent(testdir, results, fstype, "atwo", "in", "File not deleted")
            assertIsNotPresent(testdir, results, fstype, "athree", "in", "File not deleted")
            assertIsNotPresent(testdir, results, fstype, "afour", "in", "File not deleted")
            assertIsNotPresent(testdir, results, fstype, "afive", "in", "File not deleted")
            assertIsPresent(testdir, results, fstype, "xnone", "ctl", "File incorrectly deleted")

        elif testname == "wild_three":
            self.assertIn("Delete Operation Success", results)
            assertIsNotPresent(testdir, results, fstype, "abc001", "txt", "File not deleted")
            assertIsNotPresent(testdir, results, fstype, "abc002", "txt", "File not deleted")
            assertIsNotPresent(testdir, results, fstype, "abc003", "txt", "File not deleted")
            assertIsNotPresent(testdir, results, fstype, "abc004", "txt", "File not deleted")
            assertIsNotPresent(testdir, results, fstype, "abc005", "txt", "File not deleted")
            assertIsNotPresent(testdir, results, fstype, "abc010", "txt", "File not deleted")
            assertIsPresent(testdir, results, fstype, "xbc007", "txt", "File incorrectly deleted")

    def test_fat_fcb_delete_simple(self):
        """FAT FCB file delete simple"""
        self._test_fcb_delete_common("FAT", "simple")

    def test_mfs_fcb_delete_simple(self):
        """MFS FCB file delete simple"""
        self._test_fcb_delete_common("MFS", "simple")

    def test_fat_fcb_delete_missing(self):
        """FAT FCB file delete missing"""
        self._test_fcb_delete_common("FAT", "missing")

    def test_mfs_fcb_delete_missing(self):
        """MFS FCB file delete missing"""
        self._test_fcb_delete_common("MFS", "missing")

    def test_fat_fcb_delete_wild_1(self):
        """FAT FCB file delete wildcard one"""
        self._test_fcb_delete_common("FAT", "wild_one")

    def test_mfs_fcb_delete_wild_1(self):
        """MFS FCB file delete wildcard one"""
        self._test_fcb_delete_common("MFS", "wild_one")

    def test_fat_fcb_delete_wild_2(self):
        """FAT FCB file delete wildcard two"""
        self._test_fcb_delete_common("FAT", "wild_two")

    def test_mfs_fcb_delete_wild_2(self):
        """MFS FCB file delete wildcard two"""
        self._test_fcb_delete_common("MFS", "wild_two")

    def test_fat_fcb_delete_wild_3(self):
        """FAT FCB file delete wildcard three"""
        self._test_fcb_delete_common("FAT", "wild_three")

    def test_mfs_fcb_delete_wild_3(self):
        """MFS FCB file delete wildcard three"""
        self._test_fcb_delete_common("MFS", "wild_three")

    def _test_fcb_find_common(self, fstype, testname):
        testdir = self.mkworkdir('d')

        if testname == "simple":
            ename = "fcbfind1"
            fn1 = "testa"
            fe1 = "bat"
            self.mkfile(fn1 + "." + fe1, """hello\r\n""", testdir)
        elif testname == "missing":
            ename = "fcbfind2"
            fn1 = "testa"
            fe1 = "bat"
        elif testname == "wild_one":
            ename = "fcbfind3"
            fn1 = "*"
            fe1 = "in"
            for f in ["one.in", "two.in", "three.in", "four.in", "five.in",
                      "none.ctl"]:
                self.mkfile(f, """hello\r\n""", testdir)
        elif testname == "wild_two":
            ename = "fcbfind4"
            fn1 = "a*"
            fe1 = "*"
            for f in ["aone.in", "atwo.in", "athree.in", "afour.in",
                      "afive.in", "xnone.ctl"]:
                self.mkfile(f, """hello\r\n""", testdir)
        elif testname == "wild_three":
            # To find "abc001.txt ... abc099.txt"
            ename = "fcbfind5"
            fn1 = "abc0??"
            fe1 = "*"
            for f in ["abc001.txt", "abc002.txt", "abc003.txt", "abc004.txt",
                      "abc005.txt", "abc010.txt", "xbc007.txt"]:
                self.mkfile(f, """hello\r\n""", testdir)

        self.mkfile("testit.bat", """\
d:
c:\\%s
DIR
rem end
""" % ename, newline="\r\n")

        # compile sources
        self.mkcom_with_nasm(ename, r"""
bits 16
cpu 386

org 100h

section .text

    push    cs
    pop     ds

    ; Get DTA -> ES:BX
    mov     ax, 2f00h
    int     21h
    push    es
    push    bx
    pop     long [pdta]

    ; FindFirst
findfirst:
    mov     ax, 1100h
    mov     dx, fcb
    int     21h

    cmp     al, 0
    je      prsucc

prfail:
    mov     dx, failmsg
    mov     ah, 9
    int     21h
    jmp     exit

prsucc:
    mov     dx, succmsg
    mov     ah, 9
    int     21h

prfilename:
    push    ds
    lds     si, [pdta]
    inc     si

    push    cs
    pop     es
    mov     di, prires
    inc     di

    mov     cx, 11
    cld
    repne   movsb

    pop     ds
    mov     dx, prires
    mov     ah, 9
    int     21h

    ; FindNext
findnext:
    mov     ax, 1200h
    mov     dx, fcb
    int     21h

    cmp     al, 0
    je      prfilename

exit:
    mov     ah, 4ch
    int     21h

section .data

fcb:
    db  0       ; 0 default drive
fn1:
    db  "% -8s"    ; 8 bytes
fe1:
    db  "% -3s"    ; 3 bytes
wk1:
    times 25 db 0

pdta:
    dd   0

prires:
    db  "("
fname:
    times 8 db 20
fext:
    times 3 db 20
    db  ')',13,10,'$'

succmsg:
    db  "Find Operation Success",13,10,'$'
failmsg:
    db  "Find Operation Failed",13,10,'$'

""" % (fn1, fe1))

        if fstype == "MFS":
            results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
""")
        else:       # FAT
            name = self.mkimage("12", cwd=testdir)
            results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 %s +1"
$_floppy_a = ""
""" % name)

        if testname == "simple":
            self.assertIn("Find Operation Success", results)
            self.assertIn("(TESTA   BAT)", results)

        elif testname == "missing":
            self.assertIn("Find Operation Failed", results)

        elif testname == "wild_one":
            self.assertIn("Find Operation Success", results)
            self.assertIn("(ONE     IN )", results)
            self.assertIn("(TWO     IN )", results)
            self.assertIn("(THREE   IN )", results)
            self.assertIn("(FOUR    IN )", results)
            self.assertIn("(FIVE    IN )", results)
            self.assertNotIn("(NONE    CTL)", results)

        elif testname == "wild_two":
            self.assertIn("Find Operation Success", results)
            self.assertIn("(AONE    IN )", results)
            self.assertIn("(ATWO    IN )", results)
            self.assertIn("(ATHREE  IN )", results)
            self.assertIn("(AFOUR   IN )", results)
            self.assertIn("(AFIVE   IN )", results)
            self.assertNotIn("(XNONE   CTL)", results)

        elif testname == "wild_three":
            self.assertIn("Find Operation Success", results)
            self.assertIn("(ABC001  TXT)", results)
            self.assertIn("(ABC002  TXT)", results)
            self.assertIn("(ABC003  TXT)", results)
            self.assertIn("(ABC004  TXT)", results)
            self.assertIn("(ABC005  TXT)", results)
            self.assertIn("(ABC010  TXT)", results)
            self.assertNotIn("(XBC007  TXT)", results)

    def test_fat_fcb_find_simple(self):
        """FAT FCB file find simple"""
        self._test_fcb_find_common("FAT", "simple")

    def test_mfs_fcb_find_simple(self):
        """MFS FCB file find simple"""
        self._test_fcb_find_common("MFS", "simple")

    def test_fat_fcb_find_missing(self):
        """FAT FCB file find missing"""
        self._test_fcb_find_common("FAT", "missing")

    def test_mfs_fcb_find_missing(self):
        """MFS FCB file find missing"""
        self._test_fcb_find_common("MFS", "missing")

    def test_fat_fcb_find_wild_1(self):
        """FAT FCB file find wildcard one"""
        self._test_fcb_find_common("FAT", "wild_one")

    def test_mfs_fcb_find_wild_1(self):
        """MFS FCB file find wildcard one"""
        self._test_fcb_find_common("MFS", "wild_one")

    def test_fat_fcb_find_wild_2(self):
        """FAT FCB file find wildcard two"""
        self._test_fcb_find_common("FAT", "wild_two")

    def test_mfs_fcb_find_wild_2(self):
        """MFS FCB file find wildcard two"""
        self._test_fcb_find_common("MFS", "wild_two")

    def test_fat_fcb_find_wild_3(self):
        """FAT FCB file find wildcard three"""
        self._test_fcb_find_common("FAT", "wild_three")

    def test_mfs_fcb_find_wild_3(self):
        """MFS FCB file find wildcard three"""
        self._test_fcb_find_common("MFS", "wild_three")

    def _test_ds2_read_eof(self, fstype):
        testdir = self.mkworkdir('d')

        ename = "ds2rdeof"

        if fstype == "MFS":
            config = """\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
"""
        else:       # FAT
            config = """\
$_hdimage = "dXXXXs/c:hdtype1 %s +1"
$_floppy_a = ""
""" % self.mkimage("12", cwd=testdir)

        testdata = mkstring(32)

        self.mkfile("testit.bat", """\
d:
echo %s > test.fil
c:\\%s
DIR
rem end
""" % (testdata, ename), newline="\r\n")

        # compile sources
        self.mkcom_with_nasm(ename, r"""
bits 16
cpu 386

org 100h

section .text

    push    cs
    pop     ds

    mov     ax, 3d00h			; open file readonly
    mov     dx, fname
    int     21h
    jc      prfailopen

    mov     word [fhndl], ax

    mov     ax, 3f00h			; read from file, should be partial (35)
    mov     bx, word [fhndl]
    mov     cx, 64
    mov     dx, fdata
    int     21h
    jc      prfailread
    cmp     ax, 35
    jne     prnumread

    mov     ax, 3f00h			; read from file again to get EOF
    mov     bx, word [fhndl]
    mov     cx, 64
    mov     dx, fdata
    int     21h
    jc      prcarryset
    cmp     ax, 0
    jne     praxnotzero

    jmp     prsucc

prfailopen:
    mov     dx, failopen
    jmp     @1

prfailread:
    mov     dx, failread
    jmp     @2

prnumread:
    mov     dx, numread
    jmp     @2

praxnotzero:
    mov     dx, axnotzero
    jmp     @2

prcarryset:
    mov     dx, carryset
    jmp     @2

prsucc:
    mov     byte [fdata + 32], ')'
    mov     byte [fdata + 33], 13
    mov     byte [fdata + 34], 10
    mov     byte [fdata + 35], '$'
    mov     dx, success
    jmp     @2

@2:
    mov     ax, 3e00h			; close file
    mov     bx, word [fhndl]
    int     21h

@1:
    mov     ah, 9               ; print string
    int     21h

exit:
    mov     ah, 4ch
    int     21h

section .data

fname:
    db  "%s",0

fhndl:
    dw  0

success:
    db  "Operation Success("
fdata:
    times 64 db 0
failopen:
    db  "Open Operation Failed",13,10,'$'
failread:
    db  "Read Operation Failed",13,10,'$'
numread:
    db  "Partial Read Not 35 Chars",13,10,'$'
carryset:
    db  "Carry Set at EOF",13,10,'$'
axnotzero:
    db  "AX Not Zero at EOF",13,10,'$'

""" % "test.fil")

        results = self.runDosemu("testit.bat", config=config)

        self.assertNotIn("Operation Failed", results)
        self.assertNotIn("Partial Read Not 35 Chars", results)
        self.assertNotIn("Carry Set at EOF", results)
        self.assertNotIn("AX Not Zero at EOF", results)
        self.assertIn("Operation Success(%s)" % testdata, results)

    def test_fat_ds2_read_eof(self):
        """FAT DOSv2 file read EOF"""
        self._test_ds2_read_eof("FAT")

    def test_mfs_ds2_read_eof(self):
        """MFS DOSv2 file read EOF"""
        self._test_ds2_read_eof("MFS")

    def _test_ds2_read_alt_dta(self, fstype):
        testdir = self.mkworkdir('d')

        ename = "ds2radta"

        if fstype == "MFS":
            config = """\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
"""
        else:       # FAT
            config = """\
$_hdimage = "dXXXXs/c:hdtype1 %s +1"
$_floppy_a = ""
""" % self.mkimage("12", cwd=testdir)

        testdata = mkstring(32)

        self.mkfile("testit.bat", """\
d:
echo %s > test.fil
c:\\%s
DIR
rem end
""" % (testdata, ename), newline="\r\n")

        # compile sources
        self.mkcom_with_nasm(ename, r"""
bits 16
cpu 386

org 100h

section .text

    push    cs
    pop     ds

    mov     ax, 1a00h			; set DTA
    mov     dx, altdta
    int     21h

    mov     ax, 2f00h			; get DTA address in ES:BX
    int     21h
    mov     ax, cs
    mov     dx, es
    cmp     dx, ax
    jne     prfaildtaset
    cmp     bx, altdta
    jne     prfaildtaset

    mov     ax, 3d00h			; open file readonly
    mov     dx, fname
    int     21h
    jc      prfailopen

    mov     word [fhndl], ax

    mov     ax, 3f00h			; read from file, should be partial (35)
    mov     bx, word [fhndl]
    mov     cx, 64
    mov     dx, fdata
    int     21h
    jc      prfailread
    cmp     ax, 35
    jne     prnumread

    jmp     prsucc

prfaildtaset:
    mov     dx, faildtaset
    jmp     @1

prfailopen:
    mov     dx, failopen
    jmp     @1

prfailread:
    mov     dx, failread
    jmp     @2

prnumread:
    mov     dx, numread
    jmp     @2

prsucc:
    mov     byte [fdata + 32], ')'
    mov     byte [fdata + 33], 13
    mov     byte [fdata + 34], 10
    mov     byte [fdata + 35], '$'
    mov     dx, success
    jmp     @2

@2:
    mov     ax, 3e00h			; close file
    mov     bx, word [fhndl]
    int     21h

@1:
    mov     ah, 9               ; print string
    int     21h

exit:
    mov     ah, 4ch
    int     21h

section .data

fname:
    db  "%s",0

fhndl:
    dw  0

success:
    db  "Operation Success("
fdata:
    times 64 db 0
faildtaset:
    db  "Set DTA Operation Failed",13,10,'$'
failopen:
    db  "Open Operation Failed",13,10,'$'
failread:
    db  "Read Operation Failed",13,10,'$'
numread:
    db  "Partial Read Not 35 Chars",13,10,'$'

altdta:
    times 128 db 0

""" % "test.fil")

        results = self.runDosemu("testit.bat", config=config)

        self.assertNotIn("Operation Failed", results)
        self.assertNotIn("Partial Read Not 35 Chars", results)
        self.assertIn("Operation Success(%s)" % testdata, results)

    def test_fat_ds2_read_alt_dta(self):
        """FAT DOSv2 file read alternate DTA"""
        self._test_ds2_read_alt_dta("FAT")

    def test_mfs_ds2_read_alt_dta(self):
        """MFS DOSv2 file read alternate DTA"""
        self._test_ds2_read_alt_dta("MFS")

    def test_fat_ds2_file_seek_set_read(self):
        """FAT DOSv2 file seek set read"""
        ds2_file_seek_read(self, "FAT", "SET")

    def test_mfs_ds2_file_seek_set_read(self):
        """MFS DOSv2 file seek set read"""
        ds2_file_seek_read(self, "MFS", "SET")

    def test_fat_ds2_file_seek_cur_read(self):
        """FAT DOSv2 file seek current read"""
        ds2_file_seek_read(self, "FAT", "CUR")

    def test_mfs_ds2_file_seek_cur_read(self):
        """MFS DOSv2 file seek current read"""
        ds2_file_seek_read(self, "MFS", "CUR")

    def test_fat_ds2_file_seek_end_read(self):
        """FAT DOSv2 file seek end read"""
        ds2_file_seek_read(self, "FAT", "END")

    def test_mfs_ds2_file_seek_end_read(self):
        """MFS DOSv2 file seek end read"""
        ds2_file_seek_read(self, "MFS", "END")

    def test_fat_ds2_file_seek_tell_end_back(self):
        """FAT DOSv2 file seek tell end back"""
        ds2_file_seek_tell(self, "FAT", "ENDBCKSML")

    def test_mfs_ds2_file_seek_tell_end_back(self):
        """MFS DOSv2 file seek tell end back"""
        ds2_file_seek_tell(self, "MFS", "ENDBCKSML")

    def test_fat_ds2_file_seek_tell_end_back_large(self):
        """FAT DOSv2 file seek tell end back large"""
        ds2_file_seek_tell(self, "FAT", "ENDBCKLRG")

    def test_mfs_ds2_file_seek_tell_end_back_large(self):
        """MFS DOSv2 file seek tell end back large"""
        ds2_file_seek_tell(self, "MFS", "ENDBCKLRG")

    def test_fat_ds2_file_seek_tell_end_forward(self):
        """FAT DOSv2 file seek tell end forward"""
        ds2_file_seek_tell(self, "FAT", "ENDFWDSML")

    def test_mfs_ds2_file_seek_tell_end_forward(self):
        """MFS DOSv2 file seek tell end forward"""
        ds2_file_seek_tell(self, "MFS", "ENDFWDSML")

    def test_fat_ds2_file_seek_tell_end_forward_large(self):
        """FAT DOSv2 file seek tell end forward large"""
        ds2_file_seek_tell(self, "FAT", "ENDFWDLRG")

    def test_mfs_ds2_file_seek_tell_end_forward_large(self):
        """MFS DOSv2 file seek tell end forward large"""
        ds2_file_seek_tell(self, "MFS", "ENDFWDLRG")

    def _test_ds2_rename_common(self, fstype, testname):
        testdir = self.mkworkdir('d')

        extrad = ""

        if testname == "file":
            ename = "mfsds2r1"
            fn1 = "testa"
            fe1 = "bat"
            fn2 = "testb"
            fe2 = "bal"
            self.mkfile(fn1 + "." + fe1, """hello\r\n""", testdir)
        elif testname == "file_src_missing":
            ename = "mfsds2r2"
            fn1 = "testa"
            fe1 = "bat"
            fn2 = "testb"
            fe2 = "bal"
        elif testname == "file_tgt_exists":
            ename = "mfsds2r3"
            fn1 = "testa"
            fe1 = "bat"
            fn2 = "testb"
            fe2 = "bal"
            self.mkfile(fn1 + "." + fe1, """hello\r\n""", testdir)
            self.mkfile(fn2 + "." + fe2, """hello\r\n""", testdir)
        elif testname == "dir":
            ename = "mfsds2r4"
            fn1 = "testa"
            fe1 = ""
            fn2 = "testb"
            fe2 = ""
            extrad = "mkdir %s\n" % fn1
        elif testname == "dir_src_missing":
            ename = "mfsds2r5"
            fn1 = "testa"
            fe1 = ""
            fn2 = "testb"
            fe2 = ""
        elif testname == "dir_tgt_exists":
            ename = "mfsds2r6"
            fn1 = "testa"
            fe1 = ""
            fn2 = "testb"
            fe2 = ""
            extrad = "mkdir %s\nmkdir %s\n" % (fn1, fn2)

        self.mkfile("testit.bat", """\
d:
%s
c:\\%s
DIR
rem end
""" % (extrad, ename), newline="\r\n")

        # compile sources
        self.mkcom_with_nasm(ename, r"""
bits 16
cpu 386

org 100h

section .text

    push    cs
    pop     ds
    push    cs
    pop     es

    mov     ax, 5600h
    mov     dx, src
    mov     di, dst
    int     21h
    jnc     prsucc

prfail:
    mov     dx, failmsg
    jmp     @1
prsucc:
    mov     dx, succmsg
@1:
    mov     ah, 9
    int     21h

exit:
    mov     ah, 4ch
    int     21h

section .data

src:
    db  "%s",0    ; Full path
dst:
    db  "%s",0    ; Full path

succmsg:
    db  "Rename Operation Success",13,10,'$'
failmsg:
    db  "Rename Operation Failed",13,10,'$'

""" % (fn1 + "." + fe1, fn2 + "." + fe2))

        def assertIsPresent(testdir, results, fstype, f, e, msg=None):
            if fstype == "MFS":
                self.assertTrue(exists(join(testdir, f + "." + e)), msg)
            else:
                self.assertRegex(results.upper(), r"%s( +|\.)%s" % (f.upper(), e.upper()), msg)

        def assertIsPresentDir(testdir, results, fstype, f, msg=None):
            if fstype == "MFS":
                self.assertTrue(exists(join(testdir, f)), msg)
            else:
                # 2019-06-27 11:29 <DIR>         DOSEMU
                # DOSEMU               <DIR>  06-27-19  5:33p
                # TESTB        <DIR>     8-17-20  2:03p
                self.assertRegex(results.upper(),
                    r"\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}\s<DIR>\s+%s"
                    r"|"
                    r"%s\s+<DIR>\s+\d{1,2}-\d{1,2}-\d{2}\s+\d+:\d+[AaPp]" % (f.upper(), f.upper()), msg)

        if fstype == "MFS":
            results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
""")
        else:       # FAT
            name = self.mkimage("12", cwd=testdir)
            results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 %s +1"
$_floppy_a = ""
""" % name)

        if testname == "file":
            self.assertIn("Rename Operation Success", results)
            assertIsPresent(testdir, results, fstype, fn2, fe2, "File not renamed")

        elif testname == "file_src_missing":
            self.assertIn("Rename Operation Failed", results)

        elif testname == "file_tgt_exists":
            self.assertIn("Rename Operation Failed", results)

        elif testname == "dir":
            self.assertIn("Rename Operation Success", results)
            assertIsPresentDir(testdir, results, fstype, fn2, "Directory not renamed")

        elif testname == "dir_src_missing":
            self.assertIn("Rename Operation Failed", results)

        elif testname == "dir_tgt_exists":
            self.assertIn("Rename Operation Failed", results)

    def test_fat_ds2_rename_file(self):
        """FAT DOSv2 rename file"""
        self._test_ds2_rename_common("FAT", "file")

    def test_mfs_ds2_rename_file(self):
        """MFS DOSv2 rename file"""
        self._test_ds2_rename_common("MFS", "file")

    def test_fat_ds2_rename_file_src_missing(self):
        """FAT DOSv2 rename file src missing"""
        self._test_ds2_rename_common("FAT", "file_src_missing")

    def test_mfs_ds2_rename_file_src_missing(self):
        """MFS DOSv2 rename file src missing"""
        self._test_ds2_rename_common("MFS", "file_src_missing")

    def test_fat_ds2_rename_file_tgt_exists(self):
        """FAT DOSv2 rename file tgt exists"""
        self._test_ds2_rename_common("FAT", "file_tgt_exists")

    def test_mfs_ds2_rename_file_tgt_exists(self):
        """MFS DOSv2 rename file tgt exists"""
        self._test_ds2_rename_common("MFS", "file_tgt_exists")

    def test_fat_ds2_rename_dir(self):
        """FAT DOSv2 rename dir"""
        self._test_ds2_rename_common("FAT", "dir")

    def test_mfs_ds2_rename_dir(self):
        """MFS DOSv2 rename dir"""
        self._test_ds2_rename_common("MFS", "dir")

    def test_fat_ds2_rename_dir_src_missing(self):
        """FAT DOSv2 rename dir src missing"""
        self._test_ds2_rename_common("FAT", "dir_src_missing")

    def test_mfs_ds2_rename_dir_src_missing(self):
        """MFS DOSv2 rename dir src missing"""
        self._test_ds2_rename_common("MFS", "dir_src_missing")

    def test_fat_ds2_rename_dir_tgt_exists(self):
        """FAT DOSv2 rename dir tgt exists"""
        self._test_ds2_rename_common("FAT", "dir_tgt_exists")

    def test_mfs_ds2_rename_dir_tgt_exists(self):
        """MFS DOSv2 rename dir tgt exists"""
        self._test_ds2_rename_common("MFS", "dir_tgt_exists")

    def _test_ds2_delete_common(self, fstype, testname):
        testdir = self.mkworkdir('d')

        if testname == "file":
            ename = "mfsds2d1"
            fn1 = "testa"
            fe1 = "bat"
            self.mkfile(fn1 + "." + fe1, """hello\r\n""", dname=testdir)
        elif testname == "file_missing":
            ename = "mfsds2d2"
            fn1 = "testa"
            fe1 = "bat"

        self.mkfile("testit.bat", """\
d:
c:\\%s
DIR
rem end
""" % ename, newline="\r\n")

        # compile sources
        self.mkcom_with_nasm(ename, r"""
bits 16
cpu 386

org 100h

section .text

    push    cs
    pop     ds

    mov     ax, 4100h
    mov     dx, src
    int     21h
    jnc     prsucc

prfail:
    mov     dx, failmsg
    jmp     @1
prsucc:
    mov     dx, succmsg
@1:
    mov     ah, 9
    int     21h

exit:
    mov     ah, 4ch
    int     21h

section .data

src:
    db  "%s",0    ; Full path

succmsg:
    db  "Delete Operation Success",13,10,'$'
failmsg:
    db  "Delete Operation Failed",13,10,'$'

""" % (fn1 + "." + fe1))

        def assertIsNotPresent(testdir, results, fstype, f, e, msg=None):
            if fstype == "MFS":
                self.assertFalse(exists(join(testdir, f + "." + e)), msg)
            else:
                self.assertNotRegex(results.upper(), r"%s( +|\.)%s" % (f.upper(), e.upper()))

        if fstype == "MFS":
            results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
""")
        else:       # FAT
            name = self.mkimage("12", cwd=testdir)
            results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 %s +1"
$_floppy_a = ""
""" % name)

        if testname == "file":
            self.assertIn("Delete Operation Success", results)
            assertIsNotPresent(testdir, results, fstype, fn1, fe1, "File not deleted")

        elif testname == "file_missing":
            self.assertIn("Delete Operation Failed", results)

    def test_fat_ds2_delete_file(self):
        """FAT DOSv2 delete file"""
        self._test_ds2_delete_common("FAT", "file")

    def test_mfs_ds2_delete_file(self):
        """MFS DOSv2 delete file"""
        self._test_ds2_delete_common("MFS", "file")

    def test_fat_ds2_delete_file_missing(self):
        """FAT DOSv2 delete file missing"""
        self._test_ds2_delete_common("FAT", "file_missing")

    def test_mfs_ds2_delete_file_missing(self):
        """MFS DOSv2 delete file missing"""
        self._test_ds2_delete_common("MFS", "file_missing")

    def _test_ds2_find_common(self, fstype, testname):
        testdir = self.mkworkdir('d')

        if testname == "simple":
            ename = "ds2find1"
            fn1 = "testa"
            fe1 = "bat"
            self.mkfile(fn1 + "." + fe1, """hello\r\n""", testdir)
        elif testname == "missing":
            ename = "ds2find2"
            fn1 = "testa"
            fe1 = "bat"
        elif testname == "wild_one":
            ename = "ds2find3"
            fn1 = "*"
            fe1 = "in"
            for f in ["one.in", "two.in", "three.in", "four.in", "five.in",
                      "none.ctl"]:
                self.mkfile(f, """hello\r\n""", testdir)
        elif testname == "wild_two":
            ename = "ds2find4"
            fn1 = "a*"
            fe1 = "*"
            for f in ["aone.in", "atwo.in", "athree.in", "afour.in",
                      "afive.in", "xnone.ctl"]:
                self.mkfile(f, """hello\r\n""", testdir)
        elif testname == "wild_three":
            # To find "abc001.txt ... abc099.txt"
            ename = "ds2find5"
            fn1 = "abc0??"
            fe1 = "*"
            for f in ["abc001.txt", "abc002.txt", "abc003.txt", "abc004.txt",
                      "abc005.txt", "abc010.txt", "xbc007.txt"]:
                self.mkfile(f, """hello\r\n""", testdir)

        self.mkfile("testit.bat", """\
d:
c:\\%s
DIR
rem end
""" % ename, newline="\r\n")

        # compile sources
        self.mkcom_with_nasm(ename, r"""
bits 16
cpu 386

org 100h

section .text

    push    cs
    pop     ds

    ; Get DTA -> ES:BX
    mov     ax, 2f00h
    int     21h
    push    es
    push    bx
    pop     long [pdta]

    ; FindFirst
findfirst:
    mov     ax, 4e00h
    mov     cx, 0
    mov     dx, fpatn
    int     21h
    jnc     prsucc

prfail:
    mov     dx, failmsg
    mov     ah, 9
    int     21h
    jmp     exit

prsucc:
    mov     dx, succmsg
    mov     ah, 9
    int     21h

prfilename:
    push    ds
    lds     ax, [pdta]
    add     ax, 1eh
    mov     si, ax

    push    cs
    pop     es
    mov     di, prires + 1

    mov     cx, 13
    cld

@1:
    cmp     byte [ds:si], 0
    je      @2

    movsb
    loop    @1

@2:
    mov     byte [es:di], ')'
    mov     byte [es:di + 1], 13
    mov     byte [es:di + 2], 10
    mov     byte [es:di + 3], '$'

    pop     ds
    mov     dx, prires
    mov     ah, 9
    int     21h

    ; FindNext
findnext:
    mov     ax, 4f00h
    int     21h
    jnc     prfilename

exit:
    mov     ah, 4ch
    int     21h

section .data

fpatn:
    db  "%s",0

pdta:
    dd  0

prires:
    db  "("
    times 32 db 0

succmsg:
    db  "Findfirst Operation Success",13,10,'$'
failmsg:
    db  "Findfirst Operation Failed",13,10,'$'

""" % (fn1 + "." + fe1))

        if fstype == "MFS":
            results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
""")
        else:       # FAT
            name = self.mkimage("12", cwd=testdir)
            results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 %s +1"
$_floppy_a = ""
""" % name)

        if testname == "simple":
            self.assertIn("Findfirst Operation Success", results)
            self.assertIn("(TESTA.BAT)", results)

        elif testname == "missing":
            self.assertIn("Findfirst Operation Failed", results)

        elif testname == "wild_one":
            self.assertIn("Findfirst Operation Success", results)
            self.assertIn("(ONE.IN)", results)
            self.assertIn("(TWO.IN)", results)
            self.assertIn("(THREE.IN)", results)
            self.assertIn("(FOUR.IN)", results)
            self.assertIn("(FIVE.IN)", results)
            self.assertNotIn("(NONE.CTL)", results)

        elif testname == "wild_two":
            self.assertIn("Findfirst Operation Success", results)
            self.assertIn("(AONE.IN)", results)
            self.assertIn("(ATWO.IN)", results)
            self.assertIn("(ATHREE.IN)", results)
            self.assertIn("(AFOUR.IN)", results)
            self.assertIn("(AFIVE.IN)", results)
            self.assertNotIn("(XNONE.CTL)", results)

        elif testname == "wild_three":
            self.assertIn("Findfirst Operation Success", results)
            self.assertIn("(ABC001.TXT)", results)
            self.assertIn("(ABC002.TXT)", results)
            self.assertIn("(ABC003.TXT)", results)
            self.assertIn("(ABC004.TXT)", results)
            self.assertIn("(ABC005.TXT)", results)
            self.assertIn("(ABC010.TXT)", results)
            self.assertNotIn("(XBC007.TXT)", results)

    def test_fat_ds2_find_simple(self):
        """FAT DOSv2 file find simple"""
        self._test_ds2_find_common("FAT", "simple")

    def test_mfs_ds2_find_simple(self):
        """MFS DOSv2 file find simple"""
        self._test_ds2_find_common("MFS", "simple")

    def test_fat_ds2_find_missing(self):
        """FAT DOSv2 file find missing"""
        self._test_ds2_find_common("FAT", "missing")

    def test_mfs_ds2_find_missing(self):
        """MFS DOSv2 file find missing"""
        self._test_ds2_find_common("MFS", "missing")

    def test_fat_ds2_find_wild_1(self):
        """FAT DOSv2 file find wildcard one"""
        self._test_ds2_find_common("FAT", "wild_one")

    def test_mfs_ds2_find_wild_1(self):
        """MFS DOSv2 file find wildcard one"""
        self._test_ds2_find_common("MFS", "wild_one")

    def test_fat_ds2_find_wild_2(self):
        """FAT DOSv2 file find wildcard two"""
        self._test_ds2_find_common("FAT", "wild_two")

    def test_mfs_ds2_find_wild_2(self):
        """MFS DOSv2 file find wildcard two"""
        self._test_ds2_find_common("MFS", "wild_two")

    def test_fat_ds2_find_wild_3(self):
        """FAT DOSv2 file find wildcard three"""
        self._test_ds2_find_common("FAT", "wild_three")

    def test_mfs_ds2_find_wild_3(self):
        """MFS DOSv2 file find wildcard three"""
        self._test_ds2_find_common("MFS", "wild_three")

    def _test_ds2_find_first(self, fstype, testname):
        testdir = self.mkworkdir('d')

        ename = "ds2fndfi"

        ATTR = "0x00"

        if testname == "file_exists":
            FSPEC = r"\fileexst.ext"
        elif testname == "file_exists_as_dir":
            FSPEC = r"\fileexst.ext\somefile.ext"
        elif testname == "file_not_found":
            FSPEC = r"\Notfname.ext"
        elif testname == "no_more_files":
            FSPEC = r"\????????.??x"
        elif testname == "path_not_found_wc":
            FSPEC = r"\NotDir\????????.???"
        elif testname == "path_not_found_pl":
            FSPEC = r"\NotDir\plainfil.txt"
        elif testname == "path_exists_empty":
            FSPEC = r"\DirExist"
        elif testname == "path_exists_not_empty":
            FSPEC = r"\DirExis2"
        elif testname == "path_exists_file_not_dir":
            FSPEC = r"\DirExis2\fileexst.ext"
            ATTR = "0x10"
        elif testname == "dir_exists_pl":
            FSPEC = r"\DirExis2"
            ATTR = "0x10"
        elif testname == "dir_exists_wc":
            FSPEC = r"\Di?Exis?"
            ATTR = "0x10"
        elif testname == "dir_not_exists_pl":
            FSPEC = r"\dirNOTex"
            ATTR = "0x10"
        elif testname == "dir_not_exists_wc":
            FSPEC = r"\dirNOTex\wi??card.???"
            ATTR = "0x10"
        elif testname == "dir_not_exists_fn":
            FSPEC = r"\dirNOTex\somefile.ext"
            ATTR = "0x10"

        self.mkfile("testit.bat", """\
d:
echo hello > fileexst.ext
mkdir DirExist
mkdir DirExis2
echo hello > DirExis2\\fileexst.ext
c:\\%s
DIR
rem end
""" % ename, newline="\r\n")

        # compile sources
        self.mkcom_with_nasm(ename, r"""
bits 16
cpu 386

org 100h

section .text

    push    cs
    pop     ds

    mov     ax, 4e00h
    mov     cx, %s
    mov     dx, fspec
    int     21h

    jnc     prsucc

    cmp     ax, 2
    je      fail02

    cmp     ax, 3
    je      fail03

    cmp     ax, 12h
    je      fail12

    jmp     genfail

fail02:
    mov     dx, filenotfound
    jmp     @1

fail03:
    mov     dx, pathnotfoundmsg
    jmp     @1

fail12:
    mov     dx, nomoremsg
    jmp     @1

genfail:
    mov     dx, genfailmsg
    jmp     @1

prsucc:
    mov     dx, succmsg

@1:
    mov     ah, 9
    int     21h

exit:
    mov     ah, 4ch
    int     21h

section .data

fspec:
    db  "%s",0    ; Full path

succmsg:
    db  "FindFirst Operation Success",13,10,'$'
filenotfound:
    db  "FindFirst Operation Returned FILE_NOT_FOUND(0x02)",13,10,'$'
pathnotfoundmsg:
    db  "FindFirst Operation Returned PATH_NOT_FOUND(0x03)",13,10,'$'
nomoremsg:
    db  "FindFirst Operation Returned NO_MORE_FILES(0x12)",13,10,'$'
genfailmsg:
    db  "FindFirst Operation Returned Unexpected Errorcode",13,10,'$'

""" % (ATTR, FSPEC))

        if fstype == "MFS":
            results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
""")
        else:       # FAT
            name = self.mkimage("12", cwd=testdir)
            results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 %s +1"
$_floppy_a = ""
""" % name)

        if testname == "file_exists":
            self.assertIn("Operation Success", results)
        elif testname == "file_exists_as_dir":
            self.assertIn("Operation Returned PATH_NOT_FOUND(0x03)", results)
        elif testname == "file_not_found":  # Confirmed as not FILE_NOT_FOUND
            self.assertIn("Operation Returned NO_MORE_FILES(0x12)", results)
        elif testname == "no_more_files":
            self.assertIn("Operation Returned NO_MORE_FILES(0x12)", results)
        elif testname == "path_not_found_wc":
            self.assertIn("Operation Returned PATH_NOT_FOUND(0x03)", results)
        elif testname == "path_not_found_pl":
            self.assertIn("Operation Returned PATH_NOT_FOUND(0x03)", results)
        elif testname == "path_exists_empty":
            self.assertIn("Operation Returned NO_MORE_FILES(0x12)", results)
        elif testname == "path_exists_not_empty":
            self.assertIn("Operation Returned NO_MORE_FILES(0x12)", results)
        elif testname == "path_exists_file_not_dir":
            self.assertIn("Operation Success", results)
        elif testname == "dir_exists_pl":
            self.assertIn("Operation Success", results)
        elif testname == "dir_exists_wc":
            self.assertIn("Operation Success", results)
        elif testname == "dir_not_exists_pl":
            self.assertIn("Operation Returned NO_MORE_FILES(0x12)", results)
        elif testname == "dir_not_exists_wc":
            self.assertIn("Operation Returned PATH_NOT_FOUND(0x03)", results)
        elif testname == "dir_not_exists_fn":
            self.assertIn("Operation Returned PATH_NOT_FOUND(0x03)", results)

    def test_fat_ds2_findfirst_file_exists(self):
        """FAT DOSv2 findfirst file exists"""
        self._test_ds2_find_first("FAT", "file_exists")

    def test_mfs_ds2_findfirst_file_exists(self):
        """MFS DOSv2 findfirst file exists"""
        self._test_ds2_find_first("MFS", "file_exists")

    def test_fat_ds2_findfirst_file_exists_as_dir(self):
        """FAT DOSv2 findfirst file exists as dir"""
        self._test_ds2_find_first("FAT", "file_exists_as_dir")

    def test_mfs_ds2_findfirst_file_exists_as_dir(self):
        """MFS DOSv2 findfirst file exists as dir"""
        self._test_ds2_find_first("MFS", "file_exists_as_dir")

    def test_fat_ds2_findfirst_file_not_found(self):
        """FAT DOSv2 findfirst file not found"""
        self._test_ds2_find_first("FAT", "file_not_found")

    def test_mfs_ds2_findfirst_file_not_found(self):
        """MFS DOSv2 findfirst file not found"""
        self._test_ds2_find_first("MFS", "file_not_found")

    def test_fat_ds2_findfirst_no_more_files(self):
        """FAT DOSv2 findfirst no more files"""
        self._test_ds2_find_first("FAT", "no_more_files")

    def test_mfs_ds2_findfirst_no_more_files(self):
        """MFS DOSv2 findfirst no more files"""
        self._test_ds2_find_first("MFS", "no_more_files")

    def test_fat_ds2_findfirst_path_not_found_wc(self):
        """FAT DOSv2 findfirst path not found wildcard"""
        self._test_ds2_find_first("FAT", "path_not_found_wc")

    def test_mfs_ds2_findfirst_path_not_found_wc(self):
        """MFS DOSv2 findfirst path not found wildcard"""
        self._test_ds2_find_first("MFS", "path_not_found_wc")

    def test_fat_ds2_findfirst_path_not_found_pl(self):
        """FAT DOSv2 findfirst path not found plain"""
        self._test_ds2_find_first("FAT", "path_not_found_pl")

    def test_mfs_ds2_findfirst_path_not_found_pl(self):
        """MFS DOSv2 findfirst path not found plain"""
        self._test_ds2_find_first("MFS", "path_not_found_pl")

    def test_fat_ds2_findfirst_path_exists_empty(self):
        """FAT DOSv2 findfirst path exists empty"""
        self._test_ds2_find_first("FAT", "path_exists_empty")

    def test_mfs_ds2_findfirst_path_exists_empty(self):
        """MFS DOSv2 findfirst path exists empty"""
        self._test_ds2_find_first("MFS", "path_exists_empty")

    def test_fat_ds2_findfirst_path_exists_file_not_dir(self):
        """FAT DOSv2 findfirst path exists file not dir"""
        self._test_ds2_find_first("FAT", "path_exists_file_not_dir")

    def test_mfs_ds2_findfirst_path_exists_file_not_dir(self):
        """MFS DOSv2 findfirst path exists file not dir"""
        self._test_ds2_find_first("MFS", "path_exists_file_not_dir")

    def test_fat_ds2_findfirst_path_exists_not_empty(self):
        """FAT DOSv2 findfirst path exists not empty"""
        self._test_ds2_find_first("FAT", "path_exists_not_empty")

    def test_mfs_ds2_findfirst_path_exists_not_empty(self):
        """MFS DOSv2 findfirst path exists not empty"""
        self._test_ds2_find_first("MFS", "path_exists_not_empty")

    def test_fat_ds2_findfirst_dir_exists_pl(self):
        """FAT DOSv2 findfirst dir exists plain"""
        self._test_ds2_find_first("FAT", "dir_exists_pl")

    def test_mfs_ds2_findfirst_dir_exists_pl(self):
        """MFS DOSv2 findfirst dir exists plain"""
        self._test_ds2_find_first("MFS", "dir_exists_pl")

    def test_fat_ds2_findfirst_dir_exists_wc(self):
        """FAT DOSv2 findfirst dir exists wildcard"""
        self._test_ds2_find_first("FAT", "dir_exists_wc")

    def test_mfs_ds2_findfirst_dir_exists_wc(self):
        """MFS DOSv2 findfirst dir exists wildcard"""
        self._test_ds2_find_first("MFS", "dir_exists_wc")

    def test_fat_ds2_findfirst_dir_not_exists_pl(self):
        """FAT DOSv2 findfirst dir not exists plain"""
        self._test_ds2_find_first("FAT", "dir_not_exists_pl")

    def test_mfs_ds2_findfirst_dir_not_exists_pl(self):
        """MFS DOSv2 findfirst dir not exists plain"""
        self._test_ds2_find_first("MFS", "dir_not_exists_pl")

    def test_fat_ds2_findfirst_dir_not_exists_wc(self):
        """FAT DOSv2 findfirst dir not exists wildcard"""
        self._test_ds2_find_first("FAT", "dir_not_exists_wc")

    def test_mfs_ds2_findfirst_dir_not_exists_wc(self):
        """MFS DOSv2 findfirst dir not exists wildcard"""
        self._test_ds2_find_first("MFS", "dir_not_exists_wc")

    def test_fat_ds2_findfirst_dir_not_exists_fn(self):
        """FAT DOSv2 findfirst dir not exists filename"""
        self._test_ds2_find_first("FAT", "dir_not_exists_fn")

    def test_mfs_ds2_findfirst_dir_not_exists_fn(self):
        """MFS DOSv2 findfirst dir not exists filename"""
        self._test_ds2_find_first("MFS", "dir_not_exists_fn")

    def _test_ds2_find_mixed_wild_plain(self, fstype):
        testdir = self.mkworkdir('d')

        ename = "ds2findm"
        fsmpl = "xbc007.txt"

        for f in ["abc001.txt", "abc002.txt", "abc003.txt", "abc004.txt",
                  "abc005.txt", "abc010.txt", "xbc007.txt"]:
            self.mkfile(f, """hello\r\n""", testdir)

        self.mkfile("testit.bat", """\
d:
c:\\%s
DIR
rem end
""" % ename, newline="\r\n")

        # compile sources
        self.mkcom_with_nasm(ename, r"""
bits 16
cpu 386

org 100h

section .text

    push    cs
    pop     ds

    ; Get DTA -> ES:BX
    mov     ax, 2f00h
    int     21h
    push    es
    push    bx
    pop     long [pdta]

    ; First FindFirst
    mov     ax, 4e00h
    mov     cx, 0
    mov     dx, fwild
    int     21h

    ; Set alternate DTA
    mov     ax, 1a00h
    mov     dx, altdta
    int     21h

    ; Second FindFirst
    mov     ax, 4e00h
    mov     cx, 0
    mov     dx, fsmpl
    int     21h

    ; Set default DTA
    mov     ax, 1a00h
    lds     dx, [pdta]
    int     21h

    ; FindNext
    mov     ax, 4f00h
    int     21h
    jnc     prsucc

prfail:
    mov     dx, failmsg
    mov     ah, 9
    int     21h
    jmp     exit

prsucc:
    push    ds
    lds     ax, [pdta]
    add     ax, 1eh
    mov     si, ax

    push    cs
    pop     es
    mov     di, prires + 1

    mov     cx, 13
    cld

@1:
    cmp     byte [ds:si], 0
    je      @2

    movsb
    loop    @1

@2:
    mov     byte [es:di], ')'
    mov     byte [es:di + 1], 13
    mov     byte [es:di + 2], 10
    mov     byte [es:di + 3], '$'

    pop     ds
    mov     dx, succmsg
    mov     ah, 9
    int     21h

exit:
    mov     ah, 4ch
    int     21h

section .data

fwild:
    db "a*.txt",0
fsmpl:
    db "%s",0

altdta:
    times 0x80 db 0

pdta:
    dd  0

succmsg:
    db  "Findnext Operation Success"
prires:
    db  "("
    times 32 db 0

failmsg:
    db  "Findnext Operation Failed",13,10,'$'

""" % fsmpl)

        if fstype == "MFS":
            config="""\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
"""
        else:       # FAT
            name = self.mkimage("12", cwd=testdir)
            config="""\
$_hdimage = "dXXXXs/c:hdtype1 %s +1"
$_floppy_a = ""
""" % name

        results = self.runDosemu("testit.bat", config=config)

        self.assertNotIn("Findnext Operation Failed", results)
        self.assertRegex(results, r"Findnext Operation Success\(ABC...\.TXT\)")

    def test_fat_ds2_find_mixed_wild_plain(self):
        """FAT DOSv2 findnext intermixed wild plain"""
        self._test_ds2_find_mixed_wild_plain("FAT")

    def test_mfs_ds2_find_mixed_wild_plain(self):
        """MFS DOSv2 findnext intermixed wild plain"""
        self._test_ds2_find_mixed_wild_plain("MFS")

    def test_create_new_psp(self):
        """Create New PSP"""
        ename = "getnwpsp"
        cmdline = "COMMAND TAIL TEST"

        self.mkfile("testit.bat", """\
c:\\%s %s
rem end
""" % (ename, cmdline), newline="\r\n")

        # compile sources
        self.mkcom_with_nasm(ename, r"""
bits 16
cpu 386

org 100h

section .text

; designate target segment
    push    cs
    pop     ax
    add     ax, 0200h
    mov     es, ax

; create PSP in memory
    mov     dx, es
    mov     ax, 2600h
    int     21h

; see if the exit field is set
    cmp     word [es:0000], 20cdh
    jne     extfail

; see if the parent PSP is zero
    cmp     word [es:0016h], 0
    je      pntzero

; see if the parent PSP points to a PSP
    push    es
    push    word [es:0016h]
    pop     es
    cmp     word [es:0000h], 20cdh
    pop     es
    jne     pntnpsp

; see if the 'INT 21,RETF' is there
    cmp     word [es:0050h], 21cdh
    jne     int21ms
    cmp     byte [es:0052h], 0cbh
    jne     int21ms

; see if the cmdtail is there
    movzx   cx, byte [es:0080h]
    cmp     cx, %d
    jne     cmdlngth

    inc     cx
    mov     si, cmdline
    mov     di, 81h
    cld
    repe    cmpsb
    jne     cmdtail

success:
    mov     dx, successmsg
    jmp     exit

extfail:
    mov     dx, extfailmsg
    jmp     exit

pntzero:
    mov     dx, pntzeromsg
    jmp     exit

pntnpsp:
    mov     dx, pntnpspmsg
    jmp     exit

int21ms:
    mov     dx, int21msmsg
    jmp     exit

cmdlngth:
    mov     dx, cmdlngthmsg
    jmp     exit

cmdtail:
    mov     dx, cmdtailmsg
    jmp     exit

exit:
    mov     ah, 9
    int     21h

    mov     ah, 4ch
    int     21h

extfailmsg:
    db  "PSP exit field not set to CD20",13,10,'$'

pntzeromsg:
    db  "PSP parent is zero",13,10,'$'

pntnpspmsg:
    db  "PSP parent doesn't point to a PSP",13,10,'$'

int21msmsg:
    db  "PSP is missing INT21, RETF",13,10,'$'

cmdlngthmsg:
    db  "PSP has incorrect command length",13,10,'$'

cmdtailmsg:
    db  "PSP has incorrect command tail",13,10,'$'

successmsg:
    db  "PSP structure okay",13,10,'$'

; 05 20 54 45 53 54 0d
cmdline:
    db " %s",13

""" % (1 + len(cmdline), cmdline))

        results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 +1"
$_floppy_a = ""
""")

        self.assertIn("PSP structure okay", results)

# Tests using neither compiler nor assembler

    def test_systype(self):
        """SysType"""
        self.runDosemu("version.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 +1"
$_debug = "-D+d"
""")

        # read the logfile
        systypeline = "Not found in logfile"
        with open(self.logfiles['log'][0], "r") as f:
            for line in f:
                if "system type is" in line:
                    systypeline = line
                    break

        self.assertIn(self.systype, systypeline)

    def test_command_com_keyword_exist(self):
        """Command.com keyword exist"""
        self.mkfile("testit.bat", r"""
rem X: is a non-existent drive
if not exist X:\ANYTHING.EXE       echo 00_True
if not exist X:\NUL                echo 01_True
if not exist X:\FAKEDIR\NUL        echo 02_True

rem D: is a FAT(local) drive
D:
cd \
mkdir ISDIR
echo hello > ISDIR\EXIST.TRU
if exist D:\NUL                    echo 03_True
if not exist D:\EXIST.NOT          echo 04_True
if not exist D:\NODIR\NUL          echo 05_True
if not exist D:\NODIR\ANYTHING.EXE echo 06_True
if exist D:\ISDIR\EXIST.TRU        echo 07_True
if not exist D:\ISDIR\EXIST.NOT    echo 08_True

rem C: is an MFS(network redirected) drive
C:
cd \
mkdir ISDIR
echo hello > ISDIR\EXIST.TRU
if exist C:\NUL                    echo 09_True
if not exist C:\EXIST.NOT          echo 10_True
if not exist C:\NODIR\NUL          echo 11_True
if not exist C:\NODIR\ANYTHING.EXE echo 12_True
if exist C:\ISDIR\EXIST.TRU        echo 13_True
if not exist C:\ISDIR\EXIST.NOT    echo 14_True

rem end
""", newline="\r\n")

        testdir = self.mkworkdir('d')
        name = self.mkimage("12", cwd=testdir)

        results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 %s +1"
""" % name)

        for i in range(15):
            with self.subTest("Subtest %02d" % i):
                self.assertRegex(results, r"(?m)^%02d_True.*" % i)

    def _test_memory_dpmi_ecm(self, name):
        ename = "%s.com" % name
        edir = self.topdir / "test" / "ecm" / "dpmitest"

        call(["make", "--quiet", "-C", str(edir), ename])
        copy(edir / ename, self.workdir)

        self.mkfile("testit.bat", """\
c:\\%s
rem end
""" % name, newline="\r\n")

        return self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 +1"
$_floppy_a = ""
""")

    def test_memory_dpmi_ecm_alloc(self):
        """Memory DPMI (ECM) alloc"""
        results = self._test_memory_dpmi_ecm("dpmialoc")
# About to Execute : dpmialoc.com
# Protected mode breakpoint at 0221h.
# 32-bit code segment breakpoint at 0233h.
# Return from child process at 0403h.

# Welcome in 32-bit protected mode.
# DPMI allocation at 02121000h.
# DPMI allocation entrypoint at 00EFh:00000000h.
# Real mode procedure called at 00EFh:00000044h.
# DPMI allocation exit at 00EFh:0000006Ch.
# Hello from DPMI memory section!
# Calling real mode procedure which called callback successful.
# Child process terminated okay, back in real mode.

        self.assertRegex(results, r"Protected mode breakpoint at")
        self.assertRegex(results, r"32-bit code segment breakpoint at")
        self.assertRegex(results, r"Return from child process at")
        self.assertRegex(results, r"Welcome in 32-bit protected mode")
        self.assertRegex(results, r"Hello from DPMI memory section!")
        self.assertRegex(results, r"Calling real mode procedure which called callback successful")
        self.assertRegex(results, r"Child process terminated okay, back in real mode")
        self.assertNotIn("fail", results)
    test_memory_dpmi_ecm_alloc.dpmitest=True

    def test_memory_dpmi_ecm_mini(self):
        """Memory DPMI (ECM) mini"""
        results = self._test_memory_dpmi_ecm("dpmimini")

# About to Execute : dpmimini.com
# Protected mode breakpoint at 015Ah.
# 32-bit code segment breakpoint at 016Ch.
#
# Welcome in 32-bit protected mode.

        self.assertRegex(results, r"Protected mode breakpoint at")
        self.assertRegex(results, r"32-bit code segment breakpoint at")
        self.assertRegex(results, r"Welcome in 32-bit protected mode")
        self.assertNotIn("fail", results)
    test_memory_dpmi_ecm_mini.dpmitest=True

    def test_memory_dpmi_ecm_modeswitch(self):
        """Memory DPMI (ECM) Mode Switch"""
        results = self._test_memory_dpmi_ecm("dpmims")

# About to Execute : dpmims.com
# Protected mode breakpoint at 015Eh.
#
# Welcome in protected mode.
# Mode-switched to real mode.
# In protected mode again.

        self.assertRegex(results, r"Protected mode breakpoint at")
        self.assertRegex(results, r"Welcome in protected mode")
        self.assertRegex(results, r"Mode-switched to real mode")
        self.assertRegex(results, r"In protected mode again")
        self.assertNotIn("fail", results)
    test_memory_dpmi_ecm_modeswitch.dpmitest=True

    def test_memory_dpmi_ecm_psp(self):
        """Memory DPMI (ECM) psp"""
        results = self._test_memory_dpmi_ecm("dpmipsp")

# About to Execute : dpmipsp.com
# Protected mode breakpoint at 0221h.
# 32-bit code segment breakpoint at 0233h.
# Real mode procedure called at 0275h.
# Return from child process at 02FCh.
#
# Welcome in 32-bit protected mode.
# Calling real mode procedure which called callback successful.
# Child process terminated okay, back in real mode.

        self.assertRegex(results, r"Protected mode breakpoint at")
        self.assertRegex(results, r"32-bit code segment breakpoint at")
        self.assertRegex(results, r"Real mode procedure called at")
        self.assertRegex(results, r"Return from child process at")
        self.assertRegex(results, r"Welcome in 32-bit protected mode")
        self.assertRegex(results, r"Calling real mode procedure which called callback successful")
        self.assertRegex(results, r"Child process terminated okay, back in real mode")
        self.assertNotIn("fail", results)
    test_memory_dpmi_ecm_psp.dpmitest=True

    def test_memory_dpmi_japheth(self):
        """Memory DPMI (Japheth) ''"""
        memory_dpmi_japheth(self, '')
    test_memory_dpmi_japheth.dpmitest=True

    def test_memory_dpmi_japheth_c(self):
        """Memory DPMI (Japheth) '-c'"""
        memory_dpmi_japheth(self, '-c')
    test_memory_dpmi_japheth_c.dpmitest=True

    def test_memory_dpmi_japheth_d(self):
        """Memory DPMI (Japheth) '-d'"""
        memory_dpmi_japheth(self, '-d')
    test_memory_dpmi_japheth_d.dpmitest=True

    def test_memory_dpmi_japheth_e(self):
        """Memory DPMI (Japheth) '-e'"""
        memory_dpmi_japheth(self, '-e')
    test_memory_dpmi_japheth_e.dpmitest=True

    def test_memory_dpmi_japheth_i(self):
        """Memory DPMI (Japheth) '-i'"""
        memory_dpmi_japheth(self, '-i')
    test_memory_dpmi_japheth_i.dpmitest=True

    def test_memory_dpmi_japheth_m(self):
        """Memory DPMI (Japheth) '-m'"""
        memory_dpmi_japheth(self, '-m')
    test_memory_dpmi_japheth_m.dpmitest=True

    def test_memory_dpmi_japheth_r(self):
        """Memory DPMI (Japheth) '-r'"""
        memory_dpmi_japheth(self, '-r')
    test_memory_dpmi_japheth_r.dpmitest=True

    def test_memory_dpmi_japheth_t(self):
        """Memory DPMI (Japheth) '-t'"""
        memory_dpmi_japheth(self, '-t')
    test_memory_dpmi_japheth_t.dpmitest=True

    def test_memory_dpmi_japheth_z(self):
        """Memory DPMI (Japheth) '-z'"""
        memory_dpmi_japheth(self, '-z')
    test_memory_dpmi_japheth_z.dpmitest=True

    def test_memory_emm286_borland(self):
        """Memory EMM286 (Borland)"""

        self.unTarOrSkip("TEST_EMM286.tar", [
            ("tasm32.exe", "61c2ddb2c193f49dd29c083579ec7f47566278a7"),
            ("emm286.exe", "d8388a574f024d500515e4f0575958cf52939f26"),
            ("32rtm.exe", "720c8bdcb0b3b2634e95c89c56c0cc1573272cd9"),
        ])

        # Modify the config.sys
        contents = (self.workdir / self.confsys).read_text()
        contents = re.sub(r"device=(c:\\)?dosemu\\umb.sys", r"device=\1dosemu\\umb.sys /full", contents)
        contents = re.sub(r"devicehigh=(c:\\)?dosemu\\ems.sys", r"devicehigh=c:\\emm286.exe 2048", contents)
        self.mkfile(self.confsys, contents, newline="\r\n")

        self.mkfile("testit.bat", """\
c:\\tasm32 /h
rem end
""", newline="\r\n")

        results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 +1"
$_floppy_a = ""
""")

        # Look for last line of output to indicate successful load/run
        # /zi,/zd,/zn    Debug info: zi=full, zd=line numbers only, zn=none
        self.assertRegex(results, r"/zi.*Debug info: zi=full")

    def test_memory_ems_borland(self):
        """Memory EMS (Borland)"""
        memory_ems_borland(self)

    def test_memory_hma_a20(self):
        """Memory HMA a20 toggle"""
        memory_hma_a20(self)
    test_memory_hma_a20.hmatest = True

    def test_memory_hma_alloc(self):
        """Memory HMA allocation"""
        memory_hma_alloc(self)
    test_memory_hma_alloc.hmatest = True

    def test_memory_hma_alloc3(self):
        """Memory HMA alloc/resize/dealloc"""
        memory_hma_alloc3(self)
    test_memory_hma_alloc3.hmatest = True

    def test_memory_hma_freespace(self):
        """Memory HMA freespace"""
        memory_hma_freespace(self)
    test_memory_hma_freespace.hmatest = True

    def test_memory_hma_chain(self):
        """Memory HMA get chain"""
        memory_hma_chain(self)
    test_memory_hma_chain.hmatest = True

    def test_memory_xms(self):
        """Memory XMS"""
        memory_xms(self)
    test_memory_xms.xmstest = True

    def test_memory_dpmi10_ldt(self):
        """Memory DPMI-1.0 LDT"""
        memory_dpmi_dpmi10_ldt(self)
    test_memory_dpmi10_ldt.dpmitest = True

    def test_memory_dpmi_leak_check_nofree(self):
        """Memory DPMI Leak Check No Free"""
        memory_dpmi_leak_check(self, 'nofree')
    test_memory_dpmi_leak_check_nofree.dpmitest = True

    def test_memory_dpmi_leak_check_normal(self):
        """Memory DPMI Leak Check Normal"""
        memory_dpmi_leak_check(self, 'normal')
    test_memory_dpmi_leak_check_normal.dpmitest = True

    def test_memory_dpmi_leak_check_dos_nofree(self):
        """Memory DPMI Leak Check DOS No Free"""
        memory_dpmi_leak_check_dos(self, 'nofree')
    test_memory_dpmi_leak_check_dos_nofree.dpmitest = True

    def test_memory_dpmi_leak_check_dos_normal(self):
        """Memory DPMI Leak Check DOS Normal"""
        memory_dpmi_leak_check_dos(self, 'normal')
    test_memory_dpmi_leak_check_dos_normal.dpmitest = True

    def test_memory_uma_strategy(self):
        """Memory UMA Strategy"""
        memory_uma_strategy(self)
    test_memory_uma_strategy.umatest = True

    def test_floppy_img(self):
        """Floppy image file"""
        # Note: image must have
        # dosemu directory
        # autoexec.bat
        # version.bat

        self.unTarImageOrSkip("boot-floppy.img")

        results = self.runDosemu("version.bat", config="""\
$_hdimage = ""
$_floppy_a = "boot-floppy.img"
$_bootdrive = "a"
""")

        self.assertIn(self.version, results)

    def test_floppy_vfs(self):
        """Floppy vfs directory"""
        self.mkfile(self.confsys, """\
DOS=UMB,HIGH
lastdrive=Z
files=40
stacks=0,0
buffers=10
device=a:\\dosemu\\emufs.sys
device=a:\\dosemu\\umb.sys
devicehigh=a:\\dosemu\\ems.sys
devicehigh=a:\\dosemu\\cdrom.sys
install=a:\\dosemu\\emufs.com
shell=command.com /e:1024 /k %s
""" % self.autoexec, newline="\r\n")

        self.mkfile(self.autoexec, """\
prompt $P$G
path a:\\bin;a:\\gnu;a:\\dosemu
system -s DOSEMU_VERSION
@echo %s
""" % IPROMPT, newline="\r\n")

        results = self.runDosemu("version.bat", config="""\
$_hdimage = ""
$_floppy_a = "dXXXXs/c:fiveinch_360"
$_bootdrive = "a"
""")

        self.assertIn(self.version, results)

    def test_three_drives_vfs(self):
        """Three vfs directories configured"""
        # C exists as part of standard test
        self.mkworkdir('d')
        self.mkworkdir('e')

        results = self.runDosemu("version.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 dXXXXs/e:hdtype1 +1"
$_floppy_a = ""
""")

        self.assertIn(self.version, results)   # Just to check we booted

    def _test_fat_img_d_writable(self, fat):
        self.mkfile("testit.bat", """\
D:
mkdir test
echo hello > hello.txt
DIR
rem end
""", newline="\r\n")

        testdir = self.mkworkdir('d')

        testfil = testdir / "there.txt"
        testfil.write_text('there')

        name = self.mkimage_vbr(fat, cwd=testdir)

        results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 %s +1"
""" % name)

        # Std DOS format
        # TEST         <DIR>
        # HELLO    TXT 8
        #
        # ComCom32 format
        # 2019-06-28 22:29 <DIR>         TEST
        # 2019-06-28 22:29             8 HELLO.TXT
        self.assertRegex(results,
                r"TEST[\t ]+<DIR>"
                r"|"
                r"\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}\s<DIR>\s+TEST")
        self.assertRegex(results,
                r"HELLO[\t ]+TXT[\t ]+8"
                r"|"
                r"\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}\s+8\s+HELLO.TXT")
        self.assertRegex(results,
                r"THERE[\t ]+TXT[\t ]+5"
                r"|"
                r"\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}\s+5\s+THERE.TXT")

    def test_fat12_img_d_writable(self):
        """FAT12 image file D writable"""
        self._test_fat_img_d_writable("12")

    def test_fat16_img_d_writable(self):
        """FAT16 image file D writable"""
        self._test_fat_img_d_writable("16")

    def test_fat16b_img_d_writable(self):
        """FAT16B image file D writable"""
        self._test_fat_img_d_writable("16b")

    def test_fat32_img_d_writable(self):
        """FAT32 image file D writable"""
        self._test_fat_img_d_writable("32")

    def test_mfs_lredir_auto_hdc(self):
        """MFS lredir auto C drive redirection"""
        self.mkfile("testit.bat", "lredir\r\nrem end\r\n")

        results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 +1"
""")

# C:\>lredir
# Current Drive Redirections:
# C: = LINUX\FS\dosemu2.git\test-imagedir\dXXXXs\c\ attrib = READ/WRITE

        self.assertRegex(results, r"C: = /.*")

    def test_mfs_lredir_command(self):
        """MFS lredir command redirection"""
        self.mkfile("testit.bat", """\
lredir X: /tmp
lredir
rem end
""", newline="\r\n")

        results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 +1"
$_floppy_a = ""
$_lredir_paths = "/tmp"
""")

# A:\>lredir
# Current Drive Redirections:
# C: = LINUX\FS\dosemu2.git\test-imagedir\dXXXXs\c\ attrib = READ/WRITE
# X: = LINUX\FS\tmp\        attrib = READ/WRITE

        self.assertRegex(results, r"X: = /tmp")

    def test_mfs_lredir_command_no_perm(self):
        """MFS lredir command redirection permission fail"""
        self.mkfile("testit.bat", """\
lredir X: /tmp
lredir
rem end
""", newline="\r\n")

        results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 +1"
$_floppy_a = ""
""")

# A:\>lredir
# Current Drive Redirections:
# C: = LINUX\FS\dosemu2.git\test-imagedir\dXXXXs\c\ attrib = READ/WRITE
# X: = LINUX\FS\tmp\        attrib = READ/WRITE

        self.assertRegex(results, r"Error 5 \(access denied\) while redirecting drive X:")

# Tests using the DJGPP DOS compiler

    def test_mfs_findfile_ufs_lfn(self):
        """MFS findfile UFS LFN"""
        tests = (
            # Type, Create, Find
            ("DIR", "Program Files", "Program Files"),
            ("FILE", "verylongfilename.txt", "verylongfilename.txt"),
            ("FILE", "verylongfilename2.txt", "verylongfilename2.txt"),
            ("FILE", "space embedded filename.txt", "space embedded filename.txt"),
            ("FILE", "MixedCaseFilename.ext", "MixedCaseFilename.ext"),
        )
        mfs_findfile(self, "UFS", "LFN", tests)

    def test_mfs_findfile_ufs_sfn(self):
        """MFS findfile UFS SFN"""
        tests = (
            # Type, Create, Find
            ("DIR", "Program Files", "PROGR~-I"),
            ("FILE", "verylongfilename.txt", "VERYL~3G.TXT"),
            ("FILE", "verylongfilename2.txt", "VERYL~2N.TXT"),
            ("FILE", "space embedded filename.txt", "SPACE~L#.TXT"),
            ("FILE", "MixedCaseFilename.ext", "MIXED~G4.EXT"),
        )
        mfs_findfile(self, "UFS", "SFN", tests)

    def test_mfs_findfile_vfat_linux_mounted_lfn(self):
        """MFS findfile VFAT Linux mounted LFN"""
        tests = (
            # Type, Create, Find
            ("DIR", "Program Files", "Program Files"),
            ("FILE", "verylongfilename.txt", "verylongfilename.txt"),
            ("FILE", "verylongfilename2.txt", "verylongfilename2.txt"),
            ("FILE", "space embedded filename.txt", "space embedded filename.txt"),
            ("FILE", "MixedCaseFilename.ext", "MixedCaseFilename.ext"),
        )
        mfs_findfile(self, "VFAT", "LFN", tests)

    def test_mfs_findfile_vfat_linux_mounted_sfn(self):
        """MFS findfile VFAT Linux mounted SFN"""
        tests = (
            # Type, Create, Find
            ("DIR", "Program Files", "PROGRA~1"),
            ("FILE", "verylongfilename.txt", "VERYLO~1.TXT"),
            ("FILE", "verylongfilename2.txt", "VERYLO~2.TXT"),
            ("FILE", "space embedded filename.txt", "SPACEE~1.TXT"),
            ("FILE", "MixedCaseFilename.ext", "MIXEDC~1.EXT"),
        )
        mfs_findfile(self, "VFAT", "SFN", tests)

    def test_mfs_truename_ufs_lfn(self):
        """MFS truename UFS LFN"""
        names_to_create = (
            ("DIR", "Program Files"),
            ("DIR", "RealDir2"),
            ("DIR", "Sub"),
            ("DIR", "Sub/RealDir"),
            ("FILE", "Sub/Very Long realName"),
            ("FILE", "Sub/verylongRealname.txt"),
            ("FILE", "Sub/RealDir/Very Long realName"),
        )
        tests = (  # Note: CurDrv == D:, CurDir == \Sub

            # These LFN 7160/0 tests are proven on Win98 and are seen
            # to have the following rules:
            # 1/ Any '..' are resolved.
            # 2/ Any '.\' are stripped.
            # 3/ If no drive specification, then default drive is prepended.
            # 4/ If not absolute path, then current directory for drive
            #    is inserted between drive and relative path.
            # 5/ All path components (except final) are upcased.
            # 6/ Final path component case is preserved from the request.
            # 7/ No path component has to exist on the filesystem or
            #    is checked against it and updated for case.

            ("LFN0", r"aux",                                      r"D:/AUX"),
            # D:\Sub exists as a directory
            ("LFN0", r"nonExist",                                 r"D:\\SUB\\nonExist"),
            ("LFN0", r"\\nonExist",                               r"D:\\nonExist"),
            ("LFN0", r"\\Sub\\nonExist",                          r"D:\\SUB\\nonExist"),
            ("LFN0", r"c:nonExist",                               r"C:\\nonExist"),
            ("LFN0", r"c:\\nonExist",                             r"C:\\nonExist"),
            ("LFN0", r"c:\\RootC\\nonExist",                      r"C:\\ROOTC\\nonExist"),
            # Both D:\RealDir2 and D:\\Sub\\RealDir exist as directories
            ("LFN0", r"d:realdir",                                r"D:\\SUB\\realdir"),
            ("LFN0", r"d:\\realdir2",                             r"D:\\realdir2"),
            ("LFN0", r"d:\\realdir2\\noNexist.TxT",               r"D:\\REALDIR2\\noNexist.TxT"),
            # D:\Sub exists as a directory
            ("LFN0", r"nonExist\\NewFile.txt",                    r"D:\\SUB\\NONEXIST\\NewFile.txt"),
            ("LFN0", r"d:nonExist\\NewFile.txt",                  r"D:\\SUB\\NONEXIST\\NewFile.txt"),
            ("LFN0", r"d:\\nonExist\\NewFile.txt",                r"D:\\NONEXIST\\NewFile.txt"),
            ("LFN0", r"..\\Sub\\RealDir\\..\\NewFile.txt",        r"D:\\SUB\\NewFile.txt"),
            # D:\Program Files exists as a directory
            ("LFN0", r"D:\\progra~1",                             r"D:\\progra~1"),
            ("LFN0", r"D:\\PROGRA~1",                             r"D:\\PROGRA~1"),
            ("LFN0", r"D:\\program files",                        r"D:\\program files"),
            ("LFN0", r"D:\\PROGRAM FILES",                        r"D:\\PROGRAM FILES"),
            ("LFN0", r"D:\\Program Files",                        r"D:\\Program Files"),
            ("LFN0", r"D:\\Program Files\\NewFile.txt",           r"D:\\PROGRAM FILES\\NewFile.txt"),
            ("LFN0", r"D:\\Program Files\\NewFile.txt",           r"D:\\PROGRAM FILES\\NewFile.txt"),
            ("LFN0", r"D:\\Program Files\\NonExist\\NewFile.txt", r"D:\\PROGRAM FILES\\NONEXIST\\NewFile.txt"),

            ("LFN1", r"d:very long realname",                     r"D:\\SUB\\VERYL~CV"),
            ("LFN1", r"d:\\very long realname",                   r"ERROR: invalid component"),
            ("LFN1", r"d:\\Sub\\VERYLONGrEALNAME.TXT",            r"D:\\SUB\\VERYL~6S.TXT"),
            ("LFN1", r"D:\\program files",                        r"D:\\PROGR~-I"),
            ("LFN1", r"D:\\PROGRAM FILES",                        r"D:\\PROGR~-I"),
            ("LFN1", r"D:\\Program Files",                        r"D:\\PROGR~-I"),

            ("LFN2", r"D:\\SUB\\VERYL~CV",                        r"D:\\Sub\\Very Long realName"),
            ("LFN2", r"D:\\SUB\\VERYL~6S.TXT",                    r"D:\\Sub\\verylongRealname.txt"),
            ("LFN2", r"D:\\progr~-i",                             r"D:\\Program Files"),
            ("LFN2", r"D:\\PROGR~-I",                             r"D:\\Program Files"),
        )
        mfs_truename(self, "UFS", names_to_create, tests)

    def test_mfs_truename_ufs_sfn(self):
        """MFS truename UFS SFN"""
        names_to_create = (
            ("DIR", "Sub"),
            ("DIR", "Sub/testname"),
            ("FILE", "shrtname.txt"),
        )
        tests = (  # Note: CurDrv == D:, CurDir == \SUB
            ("SFN", r"aux", r"D:/AUX"),

            ("SFN", r"fakename", r"D:\\SUB\\FAKENAME"),           # Non existent
            ("SFN", r"\\fakename", r"D:\\FAKENAME"),              # Non existent
            ("SFN", r"\\Sub\\fakename", r"D:\\SUB\\FAKENAME"),    # Non existent
            ("SFN", r"c:fakename", r"C:\\FAKENAME"),              # Non existent
            ("SFN", r"c:\\fakename", r"C:\\FAKENAME"),            # Non existent
            ("SFN", r"c:\\Sub\\fakename", r"C:\\SUB\\FAKENAME"),  # Non existent

            ("SFN", r"testname", r"D:\\SUB\\TESTNAME"),
            ("SFN", r"\\Sub\\testname", r"D:\\SUB\\TESTNAME"),
            ("SFN", r"d:testname", r"D:\\SUB\\TESTNAME"),
            ("SFN", r"d:\\Sub\\testname", r"D:\\SUB\\TESTNAME"),

            ("SFN", r"shrtname.txt", r"D:\\SUB\\SHRTNAME.TXT"),   # Non existent
            ("SFN", r"\\shrtname.txt", r"D:\\SHRTNAME.TXT"),
            ("SFN", r"d:shrtname.txt", r"D:\\SUB\\SHRTNAME.TXT"), # Non existent
            ("SFN", r"d:\\shrtname.txt", r"D:\\SHRTNAME.TXT"),
        )
        mfs_truename(self, "UFS", names_to_create, tests)

    def test_mfs_truename_vfat_linux_mounted_lfn(self):
        """MFS truename VFAT Linux mounted LFN"""
        names_to_create = (
            ("DIR", "Program Files"),
            ("FILE", "lfnInRoot.tXt"),
            ("FILE", "Sub/verylongfilename.txt"),
            ("FILE", "Sub/verylongfilename2.txt"),
            ("FILE", "Sub/space embedded filename.txt"),
            ("FILE", "Sub/MixedCaseFilename.ext"),
            ("DIR", "Sub/test/1234567890987654321"),
            ("DIR", "Sub/abcdefgfedcba/1234567890987654321"),
            ("FILE", "Sub/1234567890987654321/abcdefgfedcba.txt"),
            ("FILE", "Sub/1234567890987654321/abcdefclash.txt"),
        )
        tests = (
            # Since Truename 0x7160/0 does not interact with the filesystem,
            # the tests on UFS should give coverage, but just do a few for
            # belt and braces.
            ("LFN0", r"D:\\progra~1",                                    r"D:\\progra~1"),
            ("LFN0", r"D:\\PROGRA~1",                                    r"D:\\PROGRA~1"),
            ("LFN0", r"D:\\program files",                               r"D:\\program files"),
            ("LFN0", r"D:\\PROGRAM FILES",                               r"D:\\PROGRAM FILES"),
            ("LFN0", r"D:\\Program Files",                               r"D:\\Program Files"),
            ("LFN0", r"D:\\Program Files\\NewFile.txt",                  r"D:\\PROGRAM FILES\\NewFile.txt"),
            ("LFN0", r"D:\\Program Files\\NewFile.txt",                  r"D:\\PROGRAM FILES\\NewFile.txt"),
            ("LFN0", r"D:\\Program Files\\NonExist\\NewFile.txt",        r"D:\\PROGRAM FILES\\NONEXIST\\NewFile.txt"),

            ("LFN1", r"X:\\lfnNotInRoot.tXt",                            r"ERROR: invalid component"),  # Non existent
            ("LFN1", r"..\\lfnNotInRoot.tXt",                            r"ERROR: invalid component"),  # Non existent
            ("LFN1", r"X:\\lfnInRoot.tXt",                               r"X:\\LFNINR~1.TXT"),
            ("LFN1", r"..\\lfnInRoot.tXt",                               r"X:\\LFNINR~1.TXT"),
            ("LFN1", r"..\\rootc\\..\\lfnInRoot.tXt",                    r"X:\\LFNINR~1.TXT"),
            ("LFN1", r"X:\\sub\\verylongfilename.txt",                   r"X:\\SUB\\VERYLO~1.TXT"),
            ("LFN1", r"X:\\sub\\verylongfilename2.txt",                  r"X:\\SUB\\VERYLO~2.TXT"),
            ("LFN1", r"X:\\sub\\space embedded filename.txt",            r"X:\\SUB\\SPACEE~1.TXT"),
            ("LFN1", r"X:\\sub\\MixedCaseFilename.ext",                  r"X:\\SUB\\MIXEDC~1.EXT"),
            ("LFN1", r"X:\\sub\\test\\1234567890987654321",              r"X:\\SUB\\TEST\\123456~1"),
            ("LFN1", r"X:\\sub\\abcdefgfedcba\\1234567890987654321",     r"X:\\SUB\\ABCDEF~1\\123456~1"),
            ("LFN1", r"X:\\sub\\1234567890987654321\\abcdefgfedcba.txt", r"X:\\SUB\\123456~1\\ABCDEF~1.TXT"),
            ("LFN1", r"X:\\sub\\1234567890987654321\\abcdefclash.txt",   r"X:\\SUB\\123456~1\\ABCDEF~2.TXT"),
            ("LFN1", r"1234567890987654321\\abcdefclash.txt",            r"X:\\SUB\\123456~1\\ABCDEF~2.TXT"),
            ("LFN1", r".\\1234567890987654321\\abcdefclash.txt",         r"X:\\SUB\\123456~1\\ABCDEF~2.TXT"),
            ("LFN1", r"..\\sub\\1234567890987654321\\abcdefclash.txt",   r"X:\\SUB\\123456~1\\ABCDEF~2.TXT"),
            ("LFN1", r"X:\\program files",                               r"X:\\PROGRA~1"),
            ("LFN1", r"X:\\PROGRAM FILES",                               r"X:\\PROGRA~1"),
            ("LFN1", r"X:\\Program Files",                               r"X:\\PROGRA~1"),

            ("LFN2", r"X:\\LFNNOT~1.TXT",                                r"ERROR: invalid component"),  # Non existent
            ("LFN2", r"X:\\LFNINR~1.TXT",                                r"X:\\lfnInRoot.tXt"),
            ("LFN2", r"X:\\sub\\VERYLO~1.TXT",                           r"X:\\Sub\\verylongfilename.txt"),
            ("LFN2", r"X:\\sub\\VERYLO~2.TXT",                           r"X:\\Sub\\verylongfilename2.txt"),
            ("LFN2", r"X:\\sub\\SPACEE~1.TXT",                           r"X:\\Sub\\space embedded filename.txt"),
            ("LFN2", r"X:\\sub\\MIXEDC~1.EXT",                           r"X:\\Sub\\MixedCaseFilename.ext"),
            ("LFN2", r"X:\\sub\\TEST\\123456~1",                         r"X:\\Sub\\test\\1234567890987654321"),
            ("LFN2", r"X:\\sub\\ABCDEF~1\\123456~1",                     r"X:\\Sub\\abcdefgfedcba\\1234567890987654321"),
            ("LFN2", r"X:\\sub\\123456~1\\ABCDEF~1.TXT",                 r"X:\\Sub\\1234567890987654321\\abcdefgfedcba.txt"),
            ("LFN2", r"X:\\sub\\123456~1\\ABCDEF~2.TXT",                 r"X:\\Sub\\1234567890987654321\\abcdefclash.txt"),
            ("LFN2", r"X:\\progra~1",                                    r"X:\\Program Files"),
            ("LFN2", r"X:\\PROGRA~1",                                    r"X:\\Program Files"),
            ("LFN2", r"X:\\PROGRA~1",                                    r"X:\\Program Files"),
        )
        mfs_truename(self, "VFAT", names_to_create, tests)

    def test_mfs_truename_vfat_linux_mounted_sfn(self):
        """MFS truename VFAT Linux mounted SFN"""
        names_to_create = (
            ("DIR", "testname"),
            ("FILE", "Sub/shrtname.txt"),
            ("FILE", "Sub/verylongfilename.txt"),
            ("FILE", "Sub/verylongfilename2.txt"),
            ("FILE", "Sub/space embedded filename.txt"),
            ("FILE", "Sub/MixedCaseFilename.ext"),
            ("DIR", "Sub/test/1234567890987654321"),
            ("DIR", "Sub/abcdefgfedcba/1234567890987654321"),
            ("FILE", "Sub/654321fedcba/abcdef123456.txt"),
            ("FILE", "Sub/654321fedcba/abcdefclash.txt"),
        )
        tests = (  # Note: CurDrv == X:, CurDir == \SUB
            ("SFN", r"X:\\testname",                r"X:\\TESTNAME"),
            ("SFN", r"..\\testname",                r"X:\\TESTNAME"),
            ("SFN", r"testname",                    r"X:\\SUB\\TESTNAME"),  # Non existent
            ("SFN", r"X:\\sub\\shrtname.txt",       r"X:\\SUB\\SHRTNAME.TXT"),
            ("SFN", r"X:\\sub\\verylo~1.txt",       r"X:\\SUB\\VERYLO~1.TXT"),
            ("SFN", r"X:\\sub\\verylo~2.txt",       r"X:\\SUB\\VERYLO~2.TXT"),
            ("SFN", r"X:\\sub\\spacee~1.txt",       r"X:\\SUB\\SPACEE~1.TXT"),
            ("SFN", r"X:\\sub\\mixedc~1.ext",       r"X:\\SUB\\MIXEDC~1.EXT"),
            ("SFN", r"X:\\sub\\test\\123456~1",     r"X:\\SUB\\TEST\\123456~1"),
            ("SFN", r"X:\\sub\\abcdef~1\\123456~1", r"X:\\SUB\\ABCDEF~1\\123456~1"),
            ("SFN", r"X:\\sub\\654321~1\\abcdef~1", r"X:\\SUB\\654321~1\\ABCDEF~1"),
            ("SFN", r"X:\\sub\\654321~1\\abcdef~2", r"X:\\SUB\\654321~1\\ABCDEF~2"),
        )
        mfs_truename(self, "VFAT", names_to_create, tests)

    def _test_mfs_file_read(self, nametype):
        if nametype == "LFN":
            testname = "verylongname.txt"
            disablelfn = ""
        elif nametype == "SFN":
            testname = "shrtname.txt"
            disablelfn = "set LFN=n"
        else:
            self.fail("Incorrect argument")

        testdata = mkstring(128)
        testdir = self.mkworkdir('d')

        self.mkfile("testit.bat", """\
%s
d:
c:\\mfsread %s %s
rem end
""" % (disablelfn, testname, testdata), newline="\r\n")

        self.mkfile(testname, testdata, dname=testdir)

        # compile sources
        self.mkexe_with_djgpp("mfsread", r"""
#include <dir.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
  char b[512];
  int f, size;

  if (argc < 1) {
    printf("missing filename argument\n");
    return 3;
  }

  f = open(argv[1], O_RDONLY | O_TEXT);
  if (f < 0) {
    printf("open failed\n");
    return 2;
  }

  size = read(f, b, sizeof(b));
  if (size < 0) {
    printf("read failed\n");
    return 1;
  }

  write(1, b, size);
  close(f);
  return 0;
}
""")

        results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
""")

        self.assertIn(testdata, results)

    def test_mfs_lfn_file_read(self):
        """MFS LFN file read"""
        self._test_mfs_file_read("LFN")

    def test_mfs_sfn_file_read(self):
        """MFS SFN file read"""
        self._test_mfs_file_read("SFN")

    def _test_mfs_file_write(self, nametype, operation):
        if nametype == "LFN":
            ename = "mfslfn"
            testname = "verylongname.txt"
            disablelfn = ""
        elif nametype == "SFN":
            ename = "mfssfn"
            testname = "shrtname.txt"
            disablelfn = "set LFN=n"
        else:
            self.fail("Incorrect argument")

        if operation == "create":
            ename += "wc"
            testprfx = ""
            openflags = "O_WRONLY | O_CREAT | O_TEXT"
            mode = ", 0222"
        elif operation == "createreadonly":
            ename += "wk"
            testprfx = ""
            openflags = "O_WRONLY | O_CREAT | O_TEXT"
            mode = ", 0444"
        elif operation == "truncate":
            ename += "wt"
            testprfx = "dummy data"
            openflags = "O_WRONLY | O_CREAT | O_TRUNC | O_TEXT"
            mode = ", 0222"
        elif operation == "append":
            ename += "wa"
            testprfx = "Original Data"
            openflags = "O_RDWR | O_APPEND | O_TEXT"
            mode = ""
        else:
            self.fail("Incorrect argument")

        testdata = mkstring(64)   # need to be fairly short to pass as arg
        testdir = self.mkworkdir('d')

        self.mkfile("testit.bat", """\
%s
d:
c:\\%s %s %s
rem end
""" % (disablelfn, ename, testname, testdata), newline="\r\n")

        if operation != "create" and operation != "createreadonly":
            self.mkfile(testname, testprfx, dname=testdir)

        # compile sources
        self.mkexe_with_djgpp(ename, r"""
#include <dir.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
  int f, size;

  if (argc < 2) {
    printf("missing filename argument\n");
    return 4;
  }

  if (argc < 3) {
    printf("missing data argument\n");
    return 3;
  }

  f = open(argv[1], %s %s);
  if (f < 0) {
    printf("open failed\n");
    return 2;
  }

  size = write(f, argv[2], strlen(argv[2]));
  if (size < strlen(argv[2])) {
    printf("write failed\n");
    return 1;
  }

  close(f);
  return 0;
}
""" % (openflags, mode))

        results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
""")

        self.assertNotIn("open failed", results)

        try:
            with open(join(testdir, testname), "r") as f:
                filedata = f.read()
                if operation == "truncate":
                    self.assertNotIn(testprfx, filedata)
                elif operation == "append":
                    self.assertIn(testprfx + testdata, filedata)
                self.assertIn(testdata, filedata)
        except IOError:
            self.fail("File not created/opened")

    def test_mfs_lfn_file_create(self):
        """MFS LFN file create"""
        self._test_mfs_file_write("LFN", "create")

    def test_mfs_sfn_file_create(self):
        """MFS SFN file create"""
        self._test_mfs_file_write("SFN", "create")

    def test_mfs_lfn_file_create_readonly(self):
        """MFS LFN file create readonly"""
        self._test_mfs_file_write("LFN", "createreadonly")

    def test_mfs_sfn_file_create_readonly(self):
        """MFS SFN file create readonly"""
        self._test_mfs_file_write("SFN", "createreadonly")

    def test_mfs_lfn_file_truncate(self):
        """MFS LFN file truncate"""
        self._test_mfs_file_write("LFN", "truncate")

    def test_mfs_sfn_file_truncate(self):
        """MFS SFN file truncate"""
        self._test_mfs_file_write("SFN", "truncate")

    def test_mfs_lfn_file_append(self):
        """MFS LFN file append"""
        self._test_mfs_file_write("LFN", "append")

    def test_mfs_sfn_file_append(self):
        """MFS SFN file append"""
        self._test_mfs_file_write("SFN", "append")

    def test_lfn_volume_info_mfs(self):
        """LFN volume info on MFS"""
        lfn_voln_info(self, "MFS")

    def test_lfn_volume_info_fat16(self):
        """LFN volume info on FAT16"""
        lfn_voln_info(self, "FAT16")

    def test_lfn_volume_info_fat32(self):
        """LFN volume info on FAT32"""
        lfn_voln_info(self, "FAT32")

    def test_int21_disk_info(self):
        """INT21 disk info"""

        path = "C:\\"

        self.mkfile("testit.bat", """\
c:\\int21dif %s
rem end
""" % path, newline="\r\n")

        # compile sources
        self.mkexe_with_djgpp("int21dif", r"""\
#include <stdio.h>
#include <stdint.h>
#include <string.h>

struct dinfo {
  uint16_t spc;
  uint16_t avail_clusters;
  uint16_t bps;
  uint16_t total_clusters;
};

#define MAXPATH 128

int main(int argc, char *argv[]) {
  struct dinfo df;
  uint8_t carry;
  uint16_t ax, bx, cx, dx;
  int len;

  if (argc < 2) {
    printf("path argument missing e.g. 'C:\\'\n");
    return 3;
  }

  len = strlen(argv[1]) + 1;
  if (len > MAXPATH) {
    printf("path argument too long\n");
    return 2;
  }

  if (argv[1][0] && argv[1][1] == ':') {
    if (argv[1][0] == 'a' || argv[1][0] == 'A')
      dx = 1;
    else if (argv[1][0] == 'b' || argv[1][0] == 'B')
      dx = 2;
    else if (argv[1][0] == 'c' || argv[1][0] == 'C')
      dx = 3;
    else if (argv[1][0] == 'd' || argv[1][0] == 'D')
      dx = 4;
    else {
      printf("Drive out of range\n");
      return 2;
    }
  } else {
    printf("Drive used is default\n");
    dx = 0; // default drive
  }

  /*
  AH = 36h
  DL = drive number (00h = default, 01h = A:, etc)

  Return:
    AX = FFFFh if invalid drive
  else
    AX = sectors per cluster
    BX = number of free clusters
    CX = bytes per sector
    DX = total clusters on drive
   */

  asm volatile("stc\n"
               "int $0x21\n"
               : "=a"(ax), "=b"(bx), "=c"(cx), "=d"(dx)
               : "a"(0x3600), "d"(dx)
               : "cc", "memory");

  if (ax == 0xffff) {
    printf("Call failed, AX = 0x%04x\n", ax);
    return 1;
  }

  printf("spc                 0x%04x\n", ax);
  printf("avail_clusters      0x%04x\n", bx);
  printf("bps                 0x%04x\n", cx);
  printf("total_clusters      0x%04x\n", dx);

  printf("avail_bytes(%llu)\n",
         (unsigned long long)ax * (unsigned long long)cx * (unsigned long long)bx);
  printf("total_bytes(%llu)\n",
         (unsigned long long)ax * (unsigned long long)cx * (unsigned long long)dx);
  return 0;
}
""")

        results = self.runDosemu("testit.bat", config="""\
$_hdimage = "dXXXXs/c:hdtype1 +1"
$_floppy_a = ""
""")

        self.assertNotIn("Call failed", results)

        fsinfo = statvfs(self.workdir)
        lfs_total = fsinfo.f_blocks * fsinfo.f_bsize
        lfs_avail = fsinfo.f_bavail * fsinfo.f_bsize

        r1 = re.compile(r'total_bytes\((\d+)\)')
        self.assertRegex(results, r1)
        t = r1.search(results)
        dfs_total = int(t.group(1))

        r2 = re.compile(r'avail_bytes\((\d+)\)')
        self.assertRegex(results, r2)
        a = r2.search(results)
        dfs_avail = int(a.group(1))

# see if we are within 5% of the values obtained from Linux
        if lfs_total > 2147450880:
            lfs_total = 2147450880
        if lfs_avail > 2147450880:
            lfs_avail = 2147450880
        msg = "total dos %d, linux %d" % (dfs_total, lfs_total)
        self.assertLessEqual(dfs_total, lfs_total * 1.05, msg)
        self.assertGreaterEqual(dfs_total, lfs_total * 0.95, msg)

        msg = "avail dos %d, linux %d" % (dfs_avail, lfs_avail)
        self.assertLessEqual(dfs_avail, lfs_avail * 1.05, msg)
        self.assertGreaterEqual(dfs_avail, lfs_avail * 0.95, msg)

    def test_lfs_disk_info_fat32(self):
        """LFS disk info FAT32"""
        lfs_disk_info(self, "FAT32")

    def test_lfs_disk_info_mfs(self):
        """LFS disk info MFS"""
        lfs_disk_info(self, "MFS")

    def test_mfs_lfs_file_info_1MiB(self):
        """MFS LFS file info (1 MiB)"""
        lfs_file_info(self, "MFS", "1MiB")

    def test_mfs_lfs_file_info_6GiB(self):
        """MFS LFS file info (6 GiB)"""
        lfs_file_info(self, "MFS", "6GiB")

    def test_mfs_lfs_file_seek_tell_set(self):
        """MFS LFS file seek tell set"""
        lfs_file_seek_tell(self, "MFS", "SET")

    def test_mfs_lfs_file_seek_tell_cur(self):
        """MFS LFS file seek tell current"""
        lfs_file_seek_tell(self, "MFS", "CUR")

    def test_mfs_lfs_file_seek_tell_end(self):
        """MFS LFS file seek tell end"""
        lfs_file_seek_tell(self, "MFS", "END")

    def _test_ds2_get_ftime(self, fstype, tstype):
        testdir = self.mkworkdir('d')

        self.mkfile("testit.bat", """\
d:
c:\\getftime %s
rem end
""" % tstype, newline="\r\n")

        # compile sources
        self.mkexe_with_djgpp("getftime", r"""

#include <dos.h>
#include <dir.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

int gettest(const char *fname) {

  int handle;
  int ret;
  unsigned int date, time;
  unsigned int year, month, day, hour, minute, second;

  ret = _dos_open(fname, O_RDONLY, &handle);
  if (ret != 0) {
    printf("FAIL: File '%s' not opened\n", fname);
    return -1;
  }
  _dos_getftime(handle, &date, &time);
  _dos_close(handle);

  year = ((date >> 9) & 0x7f) + 1980;
  month = (date >> 5) & 0x0f;
  day = (date & 0x1f);

  hour = (time >> 11) & 0x1f;
  minute = (time >> 5) & 0x3f;
  second = (time & 0x1f) * 2;

  // 2018-08-13 10:10:51
  printf("%s: %04u-%02u-%02u %02u:%02u:%02u\n",
      fname, year, month, day, hour, minute, second);

  return 0;
}

int main(int argc, char *argv[]) {
  unsigned int val;
  int ret;
  char fname[13];

  if (argc < 2) {
    printf("FAIL: missing argument\n");
    return -2;
  }

  if (strcmp(argv[1], "DATE") == 0) {

    // Year
    for (val = 1980; val <= 2099; val++) {
      sprintf(fname, "%08u.YR", val);
      if (gettest(fname) < 0)
        return -1;
    }

    // Month
    for (val = 1; val <= 12; val++) {
      sprintf(fname, "%08u.MN", val);
      if (gettest(fname) < 0)
        return -1;
    }

    // Day
    for (val = 1; val <= 31; val++) {
      sprintf(fname, "%08u.DY", val);
      if (gettest(fname) < 0)
        return -1;
    }

  } else if (strcmp(argv[1], "TIME") == 0) {

    // Hour
    for (val = 0; val <= 23; val++) {
      sprintf(fname, "%08u.HR", val);
      if (gettest(fname) < 0)
        return -1;
    }

    // Minute
    for (val = 0; val <= 59; val++) {
      sprintf(fname, "%08u.MI", val);
      if (gettest(fname) < 0)
        return -1;
    }

    // Seconds
    for (val = 0; val <= 59; val += 2) {
      sprintf(fname, "%08u.SC", val);
      if (gettest(fname) < 0)
        return -1;
    }

  } else {
    printf("FAIL: argument not DATE or TIME\n");
    return -2;
  }

  return 0;
}
""")

        def settime(fn, Y, M, D, h, m, s):
            date = datetime(year=Y, month=M, day=D,
                            hour=h, minute=m, second=s)
            modTime = mktime(date.timetuple())
            utime(join(testdir, fname), (modTime, modTime))

        YEARS = range(1980, 2099 + 1) if tstype == "DATE" else []
        for i in YEARS:
            fname = "%08d.YR" % i
            self.mkfile(fname, "some content", dname=testdir)
            settime(fname, i, 1, 1, 0, 0, 0)

        MONTHS = range(1, 12 + 1) if tstype == "DATE" else []
        for i in MONTHS:
            fname = "%08d.MN" % i
            self.mkfile(fname, "some content", dname=testdir)
            settime(fname, 1980, i, 1, 0, 0, 0)

        DAYS = range(1, 31 + 1) if tstype == "DATE" else []
        for i in DAYS:
            fname = "%08d.DY" % i
            self.mkfile(fname, "some content", dname=testdir)
            settime(fname, 1980, 1, i, 0, 0, 0)

        HOURS = range(0, 23 + 1) if tstype == "TIME" else []
        for i in HOURS:
            fname = "%08d.HR" % i
            self.mkfile(fname, "some content", dname=testdir)
            settime(fname, 1980, 1, 1, i, 0, 0)

        MINUTES = range(0, 59 + 1) if tstype == "TIME" else []
        for i in MINUTES:
            fname = "%08d.MI" % i
            self.mkfile(fname, "some content", dname=testdir)
            settime(fname, 1980, 1, 1, 0, i, 0)

        SECONDS = range(0, 59 + 1, 2) if tstype == "TIME" else []
        for i in SECONDS:
            fname = "%08d.SC" % i
            self.mkfile(fname, "some content", dname=testdir)
            settime(fname, 1980, 1, 1, 0, 0, i)

        if fstype == "MFS":
            config="""\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
"""
        else:       # FAT
            name = self.mkimage("12", cwd=testdir)
            config="""\
$_hdimage = "dXXXXs/c:hdtype1 %s +1"
$_floppy_a = ""
""" % name

        results = self.runDosemu("testit.bat", config=config)

        for i in YEARS:
            self.assertIn("%08d.YR: %04d-01-01 00:00:00" % (i, i), results)
        for i in MONTHS:
            self.assertIn("%08d.MN: 1980-%02d-01 00:00:00" % (i, i), results)
        for i in DAYS:
            self.assertIn("%08d.DY: 1980-01-%02d 00:00:00" % (i, i), results)
        for i in HOURS:
            self.assertIn("%08d.HR: 1980-01-01 %02d:00:00" % (i, i), results)
        for i in MINUTES:
            self.assertIn("%08d.MI: 1980-01-01 00:%02d:00" % (i, i), results)
        for i in SECONDS:
            self.assertIn("%08d.SC: 1980-01-01 00:00:%02d" % (i, i), results)

        self.assertNotIn("FAIL:", results)

    def test_mfs_ds2_get_ftime(self):
        """MFS DOSv2 get file time"""
        self._test_ds2_get_ftime("MFS", "DATE")
        self._test_ds2_get_ftime("MFS", "TIME")

    def test_fat_ds2_get_ftime(self):
        """FAT DOSv2 get file time"""
        # Note: we need to split this test as FAT cannot have enough files
        #       in the root directory, and mkimage can't store them in a
        #       subdirectory.
        self._test_ds2_get_ftime("FAT", "DATE")
        self._test_ds2_get_ftime("FAT", "TIME")

    def _test_ds2_set_ftime(self, fstype):
        testdir = self.mkworkdir('d')

        self.mkfile("testit.bat", """\
d:
c:\\setftime
rem end
""", newline="\r\n")

        # compile sources
        self.mkexe_with_djgpp("setftime", r"""
/* Most of this was copied from DJGPP docs at
   http://www.delorie.com/djgpp/doc/libc/libc_181.html */

#include <dos.h>
#include <dir.h>
#include <fcntl.h>
#include <stdio.h>

#define FNAME "FOO.DAT"

int settest(unsigned int year, unsigned int month, unsigned int day,
            unsigned int hour, unsigned int minute, unsigned int second) {

  int handle;
  struct find_t f;
  int ret;
  unsigned int date, time;
  unsigned int _year, _month, _day, _hour, _minute, _second;

  _dos_creat(FNAME, _A_NORMAL, &handle);
  date = ((year - 1980) << 9) | (month << 5) | day;
  time = (hour << 11) | (minute << 5) | (second / 2);
  _dos_setftime(handle, date, time);
  _dos_close(handle);

  ret = _dos_findfirst(FNAME, _A_NORMAL, &f);
  if (ret != 0) {
    printf("FAIL: File '%s' not found\n", FNAME);
    return -1;
  }

  _year = ((f.wr_date >> 9) & 0x7f) + 1980;
  _month = (f.wr_date >> 5) & 0x0f;
  _day = (f.wr_date & 0x1f);

  _hour = (f.wr_time >> 11) & 0x1f;
  _minute = (f.wr_time >> 5) & 0x3f;
  _second = (f.wr_time & 0x1f) * 2;

  second &= ~1; // DOS has 2 second resolution so round down the target

  if ((year != _year) || (month != _month) || (day != _day) ||
      (hour != _hour) || (minute != _minute) || (second != _second)) {
    printf("FAIL: mismatch year(%u -> %u), month(%u -> %u), day(%u -> %u)\n",
           year, _year, month, _month, day, _day);
    printf("               hour(%u -> %u), minute(%u -> %u), second(%u -> %u)\n",
           hour, _hour, minute, _minute, second, _second);
    return -2;
  }

  return 0;
}

int main(void) {
  unsigned int val;
  int ret;

  // Year
  for (val = 1980; val <= 2099; val++) {
    if (settest(val, 1, 1, 0, 0, 0) < 0)
      return -1;
  }
  printf("OKAY: years matched\n");

  // Month
  for (val = 1; val <= 12; val++) {
    if (settest(1980, val, 1, 0, 0, 0) < 0)
      return -1;
  }
  printf("OKAY: months matched\n");

  // Day
  for (val = 1; val <= 31; val++) {
    if (settest(1980, 1, val, 0, 0, 0) < 0)
      return -1;
  }
  printf("OKAY: days matched\n");

  // Hour
  for (val = 0; val <= 23; val++) {
    if (settest(1980, 1, 1, val, 0, 0) < 0)
      return -1;
  }
  printf("OKAY: hours matched\n");

  // Minute
  for (val = 0; val <= 59; val++) {
    if (settest(1980, 1, 1, 0, val, 0) < 0)
      return -1;
  }
  printf("OKAY: minutes matched\n");

  // Seconds
  for (val = 0; val <= 59; val++) {
    if (settest(1980, 1, 1, 0, 0, val) < 0)
      return -1;
  }
  printf("OKAY: seconds matched\n");

  printf("PASS: Set values are retrieved and matched\n");
  return 0;
}
""")

        if fstype == "MFS":
            config="""\
$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"
$_floppy_a = ""
"""
        else:       # FAT
            name = self.mkimage("12", cwd=testdir)
            config="""\
$_hdimage = "dXXXXs/c:hdtype1 %s +1"
$_floppy_a = ""
""" % name

        results = self.runDosemu("testit.bat", config=config)

        self.assertNotIn("FAIL:", results)

    def test_mfs_ds2_set_ftime(self):
        """MFS DOSv2 set file time"""
        self._test_ds2_set_ftime("MFS")

    def test_fat_ds2_set_ftime(self):
        """FAT DOSv2 set file time"""
        self._test_ds2_set_ftime("FAT")

    def test_mfs_ds2_set_fattrs(self):
        """MFS DOSv2 set file attrs"""
        tests = ('RDONLY', 'HIDDEN', 'SYSTEM')
        for t in tests:
            with self.subTest(t=t):
                ds2_set_fattrs(self, "MFS", t)

    def test_fat_ds2_set_fattrs(self):
        """FAT DOSv2 set file attrs"""
        tests = ('RDONLY', 'HIDDEN', 'SYSTEM')
        for t in tests:
            with self.subTest(t=t):
                ds2_set_fattrs(self, "FAT", t)

    def test_fat_ds3_file_access_read(self):
        """FAT DOSv3 file access read"""
        ds3_file_access(self, "FAT", "READ")

    def test_mfs_ds3_file_access_read(self):
        """MFS DOSv3 file access read"""
        ds3_file_access(self, "MFS", "READ")

    def test_fat_ds3_file_access_write(self):
        """FAT DOSv3 file access write"""
        ds3_file_access(self, "FAT", "WRITE")

    def test_mfs_ds3_file_access_write(self):
        """MFS DOSv3 file access write"""
        ds3_file_access(self, "MFS", "WRITE")

    def test_fat_ds3_file_access_read_device_readonly(self):
        """FAT DOSv3 file access read device readonly"""
        ds3_file_access(self, "FATRO", "READ")

    def test_mfs_ds3_file_access_read_device_readonly(self):
        """MFS DOSv3 file access read device readonly"""
        ds3_file_access(self, "MFSRO", "READ")

    def test_fat_ds3_file_access_write_device_readonly(self):
        """FAT DOSv3 file access write device readonly"""
        ds3_file_access(self, "FATRO", "WRITE")

    def test_mfs_ds3_file_access_write_device_readonly(self):
        """MFS DOSv3 file access write device readonly"""
        ds3_file_access(self, "MFSRO", "WRITE")

#    def test_mfs_ds3_lock_readonly(self):
#        """MFS DOSv3 lock file readonly"""
#        ds3_lock_readonly(self, "MFS")

    def test_fat_ds3_lock_readonly(self):
        """FAT DOSv3 lock file readonly"""
        ds3_lock_readonly(self, "FAT")

    def test_mfs_ds3_lock_readlckd(self):
        """MFS DOSv3 lock file read locked"""
        ds3_lock_readlckd(self, "MFS")

    def test_fat_ds3_lock_readlckd(self):
        """FAT DOSv3 lock file read locked"""
        ds3_lock_readlckd(self, "FAT")

    def test_mfs_ds3_lock_concurrent(self):
        """MFS DOSv3 lock file lock concurrent limit"""
        ds3_lock_concurrent(self, "MFS")

    def test_fat_ds3_lock_concurrent(self):
        """FAT DOSv3 lock file lock concurrent limit"""
        ds3_lock_concurrent(self, "FAT")

    def test_mfs_ds3_lock_two_handles(self):
        """MFS DOSv3 lock file lock with two handles"""
        ds3_lock_two_handles(self, "MFS")

    def test_fat_ds3_lock_two_handles(self):
        """FAT DOSv3 lock file lock with two handles"""
        ds3_lock_two_handles(self, "FAT")

    def test_mfs_ds3_lock_twice(self):
        """MFS DOSv3 lock file twice"""
        ds3_lock_twice(self, "MFS")

    def test_fat_ds3_lock_twice(self):
        """FAT DOSv3 lock file twice"""
        ds3_lock_twice(self, "FAT")

    def test_mfs_ds3_lock_writable(self):
        """MFS DOSv3 lock file writable"""
        ds3_lock_writable(self, "MFS")

    def test_fat_ds3_lock_writable(self):
        """FAT DOSv3 lock file writable"""
        ds3_lock_writable(self, "FAT")

    def test_mfs_ds3_share_open_twice(self):
        """MFS DOSv3 share open twice"""
        ds3_share_open_twice(self, "MFS")

    def test_fat_ds3_share_open_twice(self):
        """FAT DOSv3 share open twice"""
        ds3_share_open_twice(self, "FAT")

    def test_mfs_ds3_share_open_delete_one_process_ds2(self):
        """MFS DOSv3 share open delete one process DOSv2"""
        ds3_share_open_access(self, "ONE", "MFS", "DELPTH")

    def test_fat_ds3_share_open_delete_one_process_ds2(self):
        """FAT DOSv3 share open delete one process DOSv2"""
        ds3_share_open_access(self, "ONE", "FAT", "DELPTH")

    def test_mfs_ds3_share_open_delete_one_process_fcb(self):
        """MFS DOSv3 share open delete one process FCB"""
        ds3_share_open_access(self, "ONE", "MFS", "DELFCB")

    def test_fat_ds3_share_open_delete_one_process_fcb(self):
        """FAT DOSv3 share open delete one process FCB"""
        ds3_share_open_access(self, "ONE", "FAT", "DELFCB")

    def test_mfs_ds3_share_open_rename_one_process_ds2(self):
        """MFS DOSv3 share open rename one process DOSv2"""
        ds3_share_open_access(self, "ONE", "MFS", "RENPTH")

    def test_fat_ds3_share_open_rename_one_process_ds2(self):
        """FAT DOSv3 share open rename one process DOSv2"""
        ds3_share_open_access(self, "ONE", "FAT", "RENPTH")

    def test_mfs_ds3_share_open_rename_one_process_fcb(self):
        """MFS DOSv3 share open rename one process FCB"""
        ds3_share_open_access(self, "ONE", "MFS", "RENFCB")

    def test_fat_ds3_share_open_rename_one_process_fcb(self):
        """FAT DOSv3 share open rename one process FCB"""
        ds3_share_open_access(self, "ONE", "FAT", "RENFCB")

    def test_mfs_ds3_share_open_setfattrs_one_process(self):
        """MFS DOSv3 share open set file attrs one process DOSv2"""
        ds3_share_open_access(self, "ONE", "MFS", "SETATT")

    def test_fat_ds3_share_open_setfattrs_one_process(self):
        """FAT DOSv3 share open set file attrs one process DOSv2"""
        ds3_share_open_access(self, "ONE", "FAT", "SETATT")

    def test_mfs_ds3_share_open_delete_two_process_ds2(self):
        """MFS DOSv3 share open delete two process DOSv2"""
        ds3_share_open_access(self, "TWO", "MFS", "DELPTH")

    def test_fat_ds3_share_open_delete_two_process_ds2(self):
        """FAT DOSv3 share open delete two process DOSv2"""
        ds3_share_open_access(self, "TWO", "FAT", "DELPTH")

    def test_mfs_ds3_share_open_delete_two_process_fcb(self):
        """MFS DOSv3 share open delete two process FCB"""
        ds3_share_open_access(self, "TWO", "MFS", "DELFCB")

    def test_fat_ds3_share_open_delete_two_process_fcb(self):
        """FAT DOSv3 share open delete two process FCB"""
        ds3_share_open_access(self, "TWO", "FAT", "DELFCB")

    def test_mfs_ds3_share_open_rename_two_process_ds2(self):
        """MFS DOSv3 share open rename two process DOSv2"""
        ds3_share_open_access(self, "TWO", "MFS", "RENPTH")

    def test_fat_ds3_share_open_rename_two_process_ds2(self):
        """FAT DOSv3 share open rename two process DOSv2"""
        ds3_share_open_access(self, "TWO", "FAT", "RENPTH")

    def test_mfs_ds3_share_open_rename_two_process_fcb(self):
        """MFS DOSv3 share open rename two process FCB"""
        ds3_share_open_access(self, "TWO", "MFS", "RENFCB")

    def test_fat_ds3_share_open_rename_two_process_fcb(self):
        """FAT DOSv3 share open rename two process FCB"""
        ds3_share_open_access(self, "TWO", "FAT", "RENFCB")

    def test_mfs_ds3_share_open_setfattrs_two_process(self):
        """MFS DOSv3 share open set file attrs two process DOSv2"""
        ds3_share_open_access(self, "TWO", "MFS", "SETATT")

    def test_fat_ds3_share_open_setfattrs_two_process(self):
        """FAT DOSv3 share open set file attrs two process DOSv2"""
        ds3_share_open_access(self, "TWO", "FAT", "SETATT")

    def test_network_pktdriver_mtcp_builtin(self):
        """Network pktdriver mTCP built-in"""
        network_pktdriver_mtcp(self, 'builtin')
    test_network_pktdriver_mtcp_builtin.nettest = True

    def test_network_pktdriver_mtcp_ne2000(self):
        """Network pktdriver mTCP NE2000"""
        network_pktdriver_mtcp(self, 'ne2000')
    test_network_pktdriver_mtcp_ne2000.nettest = True

    def _test_cpu(self, cpu_vm, cpu_vm_dpmi, cpu_emu):
        if ('kvm' in cpu_vm or 'kvm' in cpu_vm_dpmi) and not access("/dev/kvm", W_OK|R_OK):
            self.skipTest("requires KVM")
        if cpu_vm == 'vm86' and uname()[4] != 'i686':
            self.skipTest("native vm86() only on 32bit x86")

        edir = self.topdir / "test" / "cpu"

        # Native reference file is now checked in to git and will
        # only need to be updated if the test source changes
        reffile = edir / "reffile.log"
        refoutput = []
        with reffile.open("r") as f:
            refoutput = f.readlines()

        # DOS test binary is built as part of normal build process
        copy(edir / "dosbin.exe", self.workdir / "dosbin.exe")
        dosfile = self.workdir / "dosfile.log"

        # output from DOS under test
        self.mkfile("testit.bat", """\
dosbin --common-tests > %s
rem end
""" % dosfile.name, newline="\r\n")

        self.runDosemu("testit.bat", timeout=20, config="""\
$_hdimage = "dXXXXs/c:hdtype1 +1"
$_floppy_a = ""
$_cpu_vm = "%s"
$_cpu_vm_dpmi = "%s"
$_cpuemu = (%i)
$_ignore_djgpp_null_derefs = (off)
"""%(cpu_vm, cpu_vm_dpmi, cpu_emu))

        dosoutput = []
        try:
            with dosfile.open("r") as f:
                dosoutput = f.readlines()
        except FileNotFoundError:
            pass
        if not dosoutput:
            self.fail("DOS output file not found")

        # Compare DOS output to reference file
        if dosoutput != refoutput:
            diff = unified_diff(refoutput, dosoutput, fromfile=str(reffile), tofile=str(dosfile))
            self.fail('differences detected\n' + ''.join(list(diff)))

    def test_cpu_1_vm86native(self):
        """CPU native vm86 + native DPMI (i386 only)"""
        self._test_cpu("vm86", "native", 0)
    test_cpu_1_vm86native.cputest = True

    def test_cpu_2_jitnative(self):
        """CPU JIT vm86 + native DPMI"""
        self._test_cpu("emulated", "native", 0)
    test_cpu_2_jitnative.cputest = True

    def test_cpu_jitkvm(self):
        """CPU JIT vm86 + KVM DPMI"""
        self._test_cpu("emulated", "kvm", 0)
    test_cpu_jitkvm.cputest = True

    def test_cpu_simnative(self):
        """CPU simulated vm86 + native DPMI"""
        self._test_cpu("emulated", "native", 1)
    test_cpu_simnative.cputest = True

    def test_cpu_simkvm(self):
        """CPU simulated vm86 + KVM DPMI"""
        self._test_cpu("emulated", "kvm", 1)
    test_cpu_simkvm.cputest = True

    def test_cpu_kvmnative(self):
        """CPU KVM vm86 + native DPMI"""
        self._test_cpu("kvm", "native", 0)
    test_cpu_kvmnative.cputest = True

    def test_cpu_kvm(self):
        """CPU KVM vm86 + KVM DPMI"""
        self._test_cpu("kvm", "kvm", 0)
    test_cpu_kvm.cputest = True

    def test_cpu_kvmjit(self):
        """CPU KVM vm86 + JIT DPMI"""
        self._test_cpu("kvm", "emulated", 0)
    test_cpu_kvmjit.cputest = True

    def test_cpu_kvmsim(self):
        """CPU KVM vm86 + simulated DPMI"""
        self._test_cpu("kvm", "emulated", 1)
    test_cpu_kvmsim.cputest = True

    def test_cpu_jit(self):
        """CPU JIT vm86 + JIT DPMI"""
        self._test_cpu("emulated", "emulated", 0)
    test_cpu_jit.cputest = True

    def test_cpu_sim(self):
        """CPU simulated vm86 + simulated DPMI"""
        self._test_cpu("emulated", "emulated", 1)
    test_cpu_sim.cputest = True

    def test_cpu_trap_flag_emulated(self):
        """CPU Trap Flag emulated"""
        cpu_trap_flag(self, 'emulated')
    test_cpu_trap_flag_emulated.cputest = True

    def test_cpu_trap_flag_kvm(self):
        """CPU Trap Flag KVM"""
        cpu_trap_flag(self, 'kvm')
    test_cpu_trap_flag_kvm.cputest = True

    def test_pcmos_build(self):
        """PC-MOS build script"""
        if environ.get("SKIP_EXPENSIVE"):
            self.skipTest("expensive test")

        mosrepo = 'https://github.com/roelandjansen/pcmos386v501.git'
        mosroot = self.imagedir / 'pcmos.git'

        try:
            run(["git", "clone", "-q", "--depth=1", mosrepo, str(mosroot)], check=True)
        except CalledProcessError:
            self.skipTest("repository unavailable")

        outfiles = [mosroot / 'SOURCES/src/latest' / x for x in [
            '$286n.sys', '$386.sys', '$all.sys', '$arnet.sys',
            '$charge.sys', '$ems.sys', '$gizmo.sys', '$kbbe.sys',
            '$kbcf.sys', '$kbdk.sys', '$kbfr.sys', '$kbgr.sys',
            '$kbit.sys', '$kbla.sys', '$kbnl.sys', '$kbno.sys',
            '$kbpo.sys', '$kbsf.sys', '$kbsg.sys', '$kbsp.sys',
            '$kbsv.sys', '$kbuk.sys', 'minbrdpc.sys', 'mosdd7f.sys',
            '$$mos.sys', '$mouse.sys', '$netbios.sys', '$pipe.sys',
            '$ramdisk.sys', '$serial.sys', '$$shell.sys']]

        for outfile in outfiles:
            outfile.unlink(missing_ok=True)

        # Run the equivalent of the MOSROOT/build.sh script from MOSROOT
        # Note:
        #     We have to avoid runDosemu() as this test is non-interactive
        args = ["-K", r".:SOURCES\src", "-E", "MAKEMOS.BAT", r"path=%D\bin;%O"]
        results = self.runDosemuCmdline(args, cwd=str(mosroot), timeout=300, config="""\
$_hdimage = "dXXXXs/c:hdtype1 +1"
$_floppy_a = ""
""")

        self.assertNotIn('Timeout', results)
        self.assertNotIn('NonZeroReturn', results)

        missing = [str(x.relative_to(mosroot)) for x in outfiles if not x.exists()]
        if len(missing):
            msg = "Output file(s) missing %s\n" % str(missing)
            raise self.failureException(msg)

    def test_passing_environment_variable(self):
        """Passing Environment Variable to DOS"""

        tstring1 = "0123456789aBcDeF"

        self.mkfile("testit.bat", """\
@ECHO on
ECHO %TESTVAR1%
rem end
""", newline="\r\n")

        args = ["TESTVAR1=" + tstring1, "-q", "-E", "testit.bat"]
        results = self.runDosemuCmdline(args, config="""\
$_hdimage = "dXXXXs/c:hdtype1 +1"
$_floppy_a = ""
""")

        self.assertNotIn('Timeout', results)
        self.assertNotIn('NonZeroReturn', results)
        self.assertIn("rem end", results, msg="Test incomplete:\n")
        self.assertIn(tstring1, results)

    def test_passing_dos_errorlevel_back(self):
        """Passing DOS Errorlevel back"""

        self.mkcom_with_ia16("justerro", r"""
int main(int argc, char *argv[])
{
  return 53;
}
""")

        results = self.runDosemuCmdline(["-q", "-E", "justerro.com"], config="""\
$_hdimage = "dXXXXs/c:hdtype1 +1"
$_floppy_a = ""
""")

        self.assertNotIn('Timeout', results)
        self.assertIn('NonZeroReturn:53', results)

    def test_pit_mode_2(self):
        """PIT Mode 2"""
        if environ.get("SKIP_EXPENSIVE"):
            self.skipTest("expensive test")
        if environ.get("SKIP_UNCERTAIN"):
            self.skipTest("uncertain test")

        pit_mode_2(self)


class DRDOS701TestCase(OurTestCase, unittest.TestCase):
    # OpenDOS 7.01

    @classmethod
    def setUpClass(cls):
        super(DRDOS701TestCase, cls).setUpClass()
        cls.version = "Caldera OpenDOS 7.01"
        cls.prettyname = "DR-DOS-7.01"
        cls.files = [
            ("ibmbio.com", "61211eb63329a67fdd9d336271f06e1bfdab2b6f"),
            ("ibmdos.com", "52e71c8e9d74100f138071acaecdef4a79b67d3c"),
            ("command.com", "4bc38f973b622509aedad8c6af0eca59a2d90fca"),
            ("share.exe", "10f2c0e2cabe98617faa017806c80476b3b6c1e1"),
        ]
        cls.systype = SYSTYPE_DRDOS_ORIGINAL
        cls.autoexec = "dautoemu.bat"
        cls.confsys = "dconfig.sys"
        cls.bootblocks = [
            ("boot-306-4-17.blk", "1151ab9a3429163ac3ddf55b88d81359cb6975e5"),
            ("boot-615-4-17.blk", "a18ee96e63e384b766bafc4ff936e4087c31bf59"),
            ("boot-900-15-17.blk", "2ea4ea747f6e62a8ea46f14f6c9af1ad6fd0126b"),
        ]
        cls.images = [
            ("boot-floppy.img", "d38fb2dba30185ce510cf3366bd71a1cbc2635da"),
        ]
        cls.actions = {
            r"test_fat_ds3_share_open_setfattrs_(one|two)_process": KNOWNFAIL,
            r"test_..._ds3_share_open_rename_one_process_fcb": KNOWNFAIL,
            r"test_..._fcb_rename_simple": KNOWNFAIL,
            r"test_..._fcb_rename_wild_\d": KNOWNFAIL,
            "test_mfs_truename_ufs_sfn": KNOWNFAIL,
            "test_mfs_truename_vfat_linux_mounted_sfn": KNOWNFAIL,
            "test_fat32_img_d_writable": UNSUPPORTED,
            "test_lfn_volume_info_fat16": KNOWNFAIL,
            "test_lfn_volume_info_fat32": UNSUPPORTED,
            "test_lfn_volume_info_mfs": KNOWNFAIL,
            "test_lfs_disk_info_fat32": UNSUPPORTED,
            "test_floppy_vfs": KNOWNFAIL,
            "test_memory_hma_alloc3": UNSUPPORTED,
            "test_memory_hma_chain": UNSUPPORTED,
            "test_pcmos_build": KNOWNFAIL,
            "test_passing_dos_errorlevel_back": KNOWNFAIL,
            "test_fat_label_create_bpb12": KNOWNFAIL,
            "test_fat_label_create_bpb16": KNOWNFAIL,
            "test_fat_label_create_bpb32": UNSUPPORTED,
            "test_fat_label_create_on_lfns": UNSUPPORTED,
        }

        cls.setUpClassPost()

    def setUpDosAutoexec(self):
        # Use the (almost) standard shipped config
        contents = (self.topdir / "src" / "bindist" / self.autoexec).read_text()
        contents = re.sub(r"[Dd]:\\", r"c:\\", contents)
        self.mkfile(self.autoexec, contents, newline="\r\n")

    def setUpDosConfig(self):
        # Link back to std dosemu commands and scripts
        p = self.workdir / "dosemu"
        p.symlink_to(self.topdir / "commands" / "dosemu")

        # Use the (almost) standard shipped config
        contents = (self.topdir / "src" / "bindist" / self.confsys).read_text()
        contents = re.sub(r"[Dd]:\\", r"c:\\", contents)
        contents = re.sub(r"rem SWITCHES=/F", r"SWITCHES=/F", contents)
        self.mkfile(self.confsys, contents, newline="\r\n")

    def setUpDosVersion(self):
        self.mkfile("version.bat", "ver\r\nrem end\r\n")


class FRDOS120TestCase(OurTestCase, unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        super(FRDOS120TestCase, cls).setUpClass()
        cls.version = "FreeDOS kernel 2042"
        cls.prettyname = "FR-DOS-1.20"
        cls.files = [
            ("kernel.sys", "0709f4e7146a8ad9b8acb33fe3fed0f6da9cc6e0"),
            ("command.com", "0733db7babadd73a1b98e8983c83b96eacef4e68"),
            ("share.com", "cadc29d49115cb3a250f90921cca345e7c427464"),
        ]
        cls.systype = SYSTYPE_FRDOS_NEW
        cls.autoexec = "fdautoem.bat"
        cls.confsys = "fdconfig.sys"
        cls.bootblocks = [
            ("boot-302-4-17.blk", "8b5cfda502e59b067d1e34e993486440cad1d4f7"),
            ("boot-603-4-17.blk", "5c89a0c9c20ba9d581d8bf6969fda88df8ab2d45"),
            ("boot-900-15-17.blk", "523f699a79edde098fceee398b15711fac56a807"),
        ]
        cls.images = [
            ("boot-floppy.img", "c3faba3620c578b6e42a6ef26554cfc9d2ee3258"),
        ]
        cls.actions = {
            "test_fat_fcb_rename_target_exists": KNOWNFAIL,
            "test_fat_fcb_rename_source_missing": KNOWNFAIL,
            "test_fat_fcb_rename_wild_1": KNOWNFAIL,
            "test_fat_fcb_rename_wild_2": KNOWNFAIL,
            "test_fat_fcb_rename_wild_3": KNOWNFAIL,
            "test_mfs_fcb_rename_target_exists": KNOWNFAIL,
            "test_mfs_fcb_rename_source_missing": KNOWNFAIL,
            "test_mfs_fcb_rename_wild_1": KNOWNFAIL,
            "test_mfs_fcb_rename_wild_2": KNOWNFAIL,
            "test_mfs_fcb_rename_wild_3": KNOWNFAIL,
            "test_mfs_fcb_rename_wild_4": KNOWNFAIL,
            "test_fat_fcb_find_wild_1": KNOWNFAIL,
            "test_fat_fcb_find_wild_2": KNOWNFAIL,
            "test_fat_fcb_find_wild_3": KNOWNFAIL,
            "test_mfs_fcb_find_wild_1": KNOWNFAIL,
            "test_mfs_fcb_find_wild_2": KNOWNFAIL,
            "test_mfs_fcb_find_wild_3": KNOWNFAIL,
            "test_mfs_lfs_file_info_1MiB": KNOWNFAIL,
            "test_mfs_lfs_file_info_6GiB": KNOWNFAIL,
            "test_mfs_lfs_file_seek_tell_set": KNOWNFAIL,
            "test_mfs_lfs_file_seek_tell_cur": KNOWNFAIL,
            "test_mfs_lfs_file_seek_tell_end": KNOWNFAIL,
            "test_mfs_lredir_command": KNOWNFAIL,
            "test_mfs_lredir_command_no_perm": KNOWNFAIL,
            "test_fat_ds3_lock_writable": KNOWNFAIL,
            "test_fat_ds3_lock_readlckd": KNOWNFAIL,
            "test_fat_ds3_lock_two_handles": KNOWNFAIL,
            "test_mfs_truename_vfat_linux_mounted_lfn": KNOWNFAIL,
            "test_mfs_truename_vfat_linux_mounted_sfn": KNOWNFAIL,
            "test_mfs_findfile_vfat_linux_mounted_lfn": KNOWNFAIL,
            "test_mfs_findfile_vfat_linux_mounted_sfn": KNOWNFAIL,
            "test_fat_ds3_share_open_twice": KNOWNFAIL,
            r"test_fat_ds3_share_open_(delete|rename)_.*": KNOWNFAIL,
            r"test_mfs_ds3_share_open_rename_(one|two)_process_fcb": KNOWNFAIL,
            r"test_fat_ds3_share_open_setfattrs_(one|two)_process": KNOWNFAIL,
            "test_create_new_psp": KNOWNFAIL,
            "test_command_com_keyword_exist": KNOWNFAIL,
            "test_memory_emm286_borland": KNOWNFAIL,
            "test_memory_hma_alloc": KNOWNFAIL,
            "test_memory_hma_alloc3": UNSUPPORTED,
            "test_memory_hma_chain": UNSUPPORTED,
            "test_memory_uma_strategy": KNOWNFAIL,
            "test_pcmos_build": KNOWNFAIL,
            r"test_libi86_item_\d+": KNOWNFAIL,
            "test_passing_dos_errorlevel_back": KNOWNFAIL,
            "test_fat_label_create_bpb12": KNOWNFAIL,
            "test_fat_label_create_bpb16": KNOWNFAIL,
            "test_fat_label_create_bpb32": KNOWNFAIL,
            "test_fat_label_create_prefile": KNOWNFAIL,
            "test_fat_label_create_predir": KNOWNFAIL,
        }

        cls.setUpClassPost()

    def setUpDosAutoexec(self):
        # Use the (almost) standard shipped config
        contents = (self.topdir / "src" / "bindist" / self.autoexec).read_text()
        contents = re.sub(r"[Dd]:\\", r"c:\\", contents)
        self.mkfile(self.autoexec, contents, newline="\r\n")

    def setUpDosConfig(self):
        # Link back to std dosemu commands and scripts
        p = self.workdir / "dosemu"
        p.symlink_to(self.topdir / "commands" / "dosemu")

        # Use the (almost) standard shipped config
        contents = (self.topdir / "src" / "bindist" / "c" / self.confsys).read_text()
        contents = re.sub(r"[Dd]:\\", r"c:\\", contents)
        contents = re.sub(r"rem SWITCHES=/F", r"SWITCHES=/F", contents)
        self.mkfile(self.confsys, contents, newline="\r\n")


class FRDOS130TestCase(OurTestCase, unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        super(FRDOS130TestCase, cls).setUpClass()
        cls.version = "FreeDOS kernel 2043"
        cls.prettyname = "FR-DOS-1.30"
        cls.files = [
            ("kernel.sys", "2bdf90c8bc8c0406cfa01349265bf782507af016"),
            ("command.com", "15abab3d3ee4a50449517131a13b2c5164610582"),
            ("share.com", "cadc29d49115cb3a250f90921cca345e7c427464"),
        ]
        cls.systype = SYSTYPE_FRDOS_NEW
        cls.autoexec = "fdautoem.bat"
        cls.confsys = "fdconfig.sys"
        cls.bootblocks = [
            ("boot-306-4-17.blk", "0092a320500d7a8359d40bddc48f592686745aed"),
            ("boot-615-4-17.blk", "2b757178c7ba97f8a439c83dc627d61c2d6b3cf6"),
            ("boot-900-15-17.blk", "8cd7adeff4a0265e8a8e20f7942672c677cbc891"),
        ]
        cls.images = [
            ("boot-floppy.img", "7b68b4dc2de5891bb3700816d8e1a323e8d150bb"),
        ]
        cls.actions = {
            "test_command_com_keyword_exist": KNOWNFAIL,
            "test_create_new_psp": KNOWNFAIL,
            "test_fat_ds3_lock_readlckd": KNOWNFAIL,
            "test_fat_ds3_lock_two_handles": KNOWNFAIL,
            "test_fat_ds3_lock_writable": KNOWNFAIL,
            r"test_fat_ds3_share_open_(delete|rename)_.*": KNOWNFAIL,
            r"test_fat_ds3_share_open_setfattrs_(one|two)_process": KNOWNFAIL,
            "test_fat_ds3_share_open_twice": KNOWNFAIL,
            "test_fat_fcb_find_wild_1": KNOWNFAIL,
            "test_fat_fcb_find_wild_2": KNOWNFAIL,
            "test_fat_fcb_find_wild_3": KNOWNFAIL,
            "test_fat_fcb_rename_wild_1": KNOWNFAIL,
            "test_fat_fcb_rename_wild_2": KNOWNFAIL,
            "test_fat_fcb_rename_wild_3": KNOWNFAIL,
            "test_fat_fcb_rename_wild_4": KNOWNFAIL,
            "test_fat_label_create_bpb12": KNOWNFAIL,
            "test_fat_label_create_bpb16": KNOWNFAIL,
            "test_fat_label_create_bpb32": KNOWNFAIL,
            "test_fat_label_create_noduplicate": KNOWNFAIL,
            "test_fat_label_create_predir": KNOWNFAIL,
            "test_fat_label_create_prefile": KNOWNFAIL,
            "test_memory_emm286_borland": KNOWNFAIL,
            "test_memory_hma_alloc": KNOWNFAIL,
            "test_memory_hma_alloc3": UNSUPPORTED,
            "test_memory_hma_chain": UNSUPPORTED,
            "test_memory_uma_strategy": KNOWNFAIL,
            "test_mfs_fcb_rename_wild_1": KNOWNFAIL,
            "test_mfs_fcb_rename_wild_2": KNOWNFAIL,
            "test_mfs_fcb_rename_wild_3": KNOWNFAIL,
            "test_mfs_fcb_rename_wild_4": KNOWNFAIL,
            "test_passing_dos_errorlevel_back": KNOWNFAIL,
        }

        cls.setUpClassPost()

    def setUpDosAutoexec(self):
        # Use the (almost) standard shipped config
        contents = (self.topdir / "src" / "bindist" / self.autoexec).read_text()
        contents = re.sub(r"[Dd]:\\", r"c:\\", contents)
        self.mkfile(self.autoexec, contents, newline="\r\n")

    def setUpDosConfig(self):
        # Link back to std dosemu commands and scripts
        p = self.workdir / "dosemu"
        p.symlink_to(self.topdir / "commands" / "dosemu")

        # Use the (almost) standard shipped config
        contents = (self.topdir / "src" / "bindist" / "c" / self.confsys).read_text()
        contents = re.sub(r"[Dd]:\\", r"c:\\", contents)
        contents = re.sub(r"rem SWITCHES=/F", r"SWITCHES=/F", contents)
        self.mkfile(self.confsys, contents, newline="\r\n")


class FRDOSGITTestCase(OurTestCase, unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        super(FRDOSGITTestCase, cls).setUpClass()
        cls.version = "DOS version 7.10"
        cls.prettyname = "FR-DOS-GIT"
        cls.tarfile = ""
        cls.systype = SYSTYPE_FRDOS_NEW
        cls.autoexec = "fdautoem.bat"
        cls.confsys = "fdconfig.sys"
        cls.bootblocks = [
        ]
        cls.images = [
        ]
        cls.actions = {
            "test_fat_ds3_lock_concurrent": KNOWNFAIL,
            "test_fat_ds3_lock_readlckd": KNOWNFAIL,
            "test_fat_ds3_lock_two_handles": KNOWNFAIL,
            "test_fat_ds3_lock_writable": KNOWNFAIL,
            "test_fat_ds3_share_open_delete_one_process_ds2": KNOWNFAIL,
            "test_fat_ds3_share_open_delete_one_process_fcb": KNOWNFAIL,
            "test_fat_ds3_share_open_rename_one_process_ds2": KNOWNFAIL,
            "test_fat_ds3_share_open_rename_one_process_fcb": KNOWNFAIL,
            "test_fat_ds3_share_open_setfattrs_one_process": KNOWNFAIL,
            "test_fat_ds3_share_open_twice": KNOWNFAIL,
            "test_fat_label_create_bpb12": KNOWNFAIL,
            "test_fat_label_create_bpb16": KNOWNFAIL,
            "test_fat_label_create_bpb32": KNOWNFAIL,
            "test_fat_label_create_noduplicate": KNOWNFAIL,
            "test_fat_label_create_predir": KNOWNFAIL,
            "test_fat_label_create_prefile": KNOWNFAIL,
            "test_memory_emm286_borland": KNOWNFAIL,
            "test_memory_hma_alloc3": UNSUPPORTED,
            "test_memory_hma_chain": UNSUPPORTED,
            "test_memory_uma_strategy": KNOWNFAIL,
            "test_passing_dos_errorlevel_back": KNOWNFAIL,
        }

        cls.setUpClassPost()

        # Check files under test exist, or skip the whole thing
        cls.files_to_copy = [
            Path(environ.get("FDOS_KERNEL_SYS", "../fdos/kernel.git/bin/kernel.sys")),
            Path(environ.get("FDOS_COMMAND_COM", "../fdos/freecom.git/command.com")),
            Path(environ.get("FDOS_SHARE_COM", "../fdos/share.git/src/share.com")),
        ]
        for f in cls.files_to_copy:
            if not f.is_file():
                raise unittest.SkipTest("File '%s' not found" % f.name)

    def setUp(self):
        super(FRDOSGITTestCase, self).setUp()
        for f in self.files_to_copy:
            copy(f, self.workdir)

    def setUpDosAutoexec(self):
        # Use the (almost) standard shipped config
        contents = (self.topdir / "src" / "bindist" / self.autoexec).read_text()
        contents = re.sub(r"[Dd]:\\", r"c:\\", contents)
        self.mkfile(self.autoexec, contents, newline="\r\n")

    def setUpDosConfig(self):
        # Link back to std dosemu commands and scripts
        p = self.workdir / "dosemu"
        p.symlink_to(self.topdir / "commands" / "dosemu")

        # Use the (almost) standard shipped config
        contents = (self.topdir / "src" / "bindist" / "c" / self.confsys).read_text()
        contents = re.sub(r"[Dd]:\\", r"c:\\", contents)
        contents = re.sub(r"rem SWITCHES=/F", r"SWITCHES=/F", contents)
        self.mkfile(self.confsys, contents, newline="\r\n")


class MSDOS622TestCase(OurTestCase, unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        super(MSDOS622TestCase, cls).setUpClass()
        cls.version = "MS-DOS Version 6.22"
        cls.prettyname = "MS-DOS-6.22"
        cls.files = [
            ("io.sys", "d697961ca6edaf9a1aafe8b7eb949597506f7f95"),
            ("msdos.sys", "d6a5f54006e69c4407e56677cd77b82395acb60a"),
            ("command.com", "c2179d2abfa241edd388ab875cfabbac89fec44d"),
            ("share.exe", "9e7385cfa91a012638520e89b9884e4ce616d131"),
            ("dos/himem.sys", "fb41fbc1c4bdd8652d445055508bc8265bc64aea"),
        ]
        cls.systype = SYSTYPE_MSDOS_INTERMEDIATE
        cls.autoexec = "autoemu.bat"
        cls.bootblocks = [
            ("boot-306-4-17.blk", "d40c24ef5f5f9fd6ef28c29240786c70477a0b06"),
            ("boot-615-4-17.blk", "7fc96777727072471dbaab6f817c8d13262260d2"),
            ("boot-900-15-17.blk", "2a0ca1b87b82013fd417542a5ac28e965fb13e7a"),
        ]
        cls.images = [
            ("boot-floppy.img", "14b8310910bf19d6e375298f3b06da7ffdec9932"),
        ]
        cls.actions = {
            "test_fat32_img_d_writable": UNSUPPORTED,
            "test_lfn_volume_info_fat16": KNOWNFAIL,
            "test_lfn_volume_info_fat32": UNSUPPORTED,
            "test_lfs_disk_info_fat32": UNSUPPORTED,
            "test_memory_hma_alloc3": UNSUPPORTED,
            "test_memory_hma_chain": UNSUPPORTED,
            "test_passing_dos_errorlevel_back": KNOWNFAIL,
            "test_fat_label_create_bpb32": UNSUPPORTED,
            "test_fat_label_create_on_lfns": UNSUPPORTED,
        }

        cls.setUpClassPost()

    def setUpDosAutoexec(self):
        # Use the (almost) standard shipped config
        contents = (self.topdir / "src" / "bindist" / self.autoexec).read_text()
        contents = re.sub(r"[Dd]:\\", r"c:\\", contents)
        self.mkfile(self.autoexec, contents, newline="\r\n")

    def setUpDosConfig(self):
        # Link back to std dosemu commands and scripts
        p = self.workdir / "dosemu"
        p.symlink_to(self.topdir / "commands" / "dosemu")

        # Use the (almost) standard shipped config
        contents = (self.topdir / "src" / "bindist" / "c" / self.confsys).read_text()
        contents = re.sub(r"[Dd]:\\", r"c:\\", contents)
        contents = re.sub(r"rem SWITCHES=/F", r"SWITCHES=/F", contents)
        self.mkfile(self.confsys, contents, newline="\r\n")

    def setUpDosVersion(self):
        self.mkfile("version.bat", "ver\r\nrem end\r\n")


class MSDOS700TestCase(OurTestCase, unittest.TestCase):
    # badged Win95 RTM at winworldpc.com

    @classmethod
    def setUpClass(cls):
        super(MSDOS700TestCase, cls).setUpClass()
        cls.version = "Windows 95. [Version 4.00.950]"
        cls.prettyname = "MS-DOS-7.00"
        cls.files = [
            ("io.sys", "22924f93dd0f9ea6a4624ccdd1bbcdf5eb43a308"),
            ("msdos.sys", "f5d01c68d518f4b8b2482d3815af8bb88003831d"),
            ("command.com", "67696207c3963a0dc9afab8cf37dbdb966c1f663"),
        ]
        cls.systype = SYSTYPE_MSDOS_NEW
        cls.autoexec = "autoemu.bat"
        cls.bootblocks = [
            ("boot-306-4-17.blk", "8c016e339ca6b8126fd2026ed3a7eeeb6cbb8903"),
            ("boot-615-4-17.blk", "b6fdddbfb37442a2762d5897de1aa7d7a694286a"),
            ("boot-900-15-17.blk", "8c1243481112f320f2a5f557f30db11174fe7e3d"),
        ]
        cls.images = [
            ("boot-floppy.img", ""),
        ]
        cls.actions = {
            "test_fat32_img_d_writable": UNSUPPORTED,
            "test_fat_label_create_bpb32": UNSUPPORTED,
            "test_lfn_volume_info_fat16": KNOWNFAIL,
            "test_lfn_volume_info_fat32": UNSUPPORTED,
            "test_lfs_disk_info_fat32": UNSUPPORTED,
            "test_lfs_disk_info_mfs": KNOWNFAIL,
        }

        cls.setUpClassPost()

    def setUpDosAutoexec(self):
        # Use the (almost) standard shipped config
        contents = (self.topdir / "src" / "bindist" / self.autoexec).read_text()
        contents = re.sub(r"[Dd]:\\", r"c:\\", contents)
        self.mkfile(self.autoexec, contents, newline="\r\n")

    def setUpDosConfig(self):
        # Link back to std dosemu commands and scripts
        p = self.workdir / "dosemu"
        p.symlink_to(self.topdir / "commands" / "dosemu")

        # Use the (almost) standard shipped config
        contents = (self.topdir / "src" / "bindist" / "c" / self.confsys).read_text()
        contents = re.sub(r"[Dd]:\\", r"c:\\", contents)
        contents = re.sub(r"rem SWITCHES=/F", r"SWITCHES=/F", contents)
        self.mkfile(self.confsys, contents, newline="\r\n")

    def setUpDosVersion(self):
        self.mkfile("version.bat", "ver\r\nrem end\r\n")

        # Disable the logo here or we get blank screen
        self.mkfile("msdos.sys", """
[Options]
BootGUI=0
Logo=0
""", newline="\r\n")


class MSDOS710TestCase(OurTestCase, unittest.TestCase):
    # badged CDU (Chinese DOS Union) at winworldpc.com

    @classmethod
    def setUpClass(cls):
        super(MSDOS710TestCase, cls).setUpClass()
        cls.version = "MS-DOS 7.1 [Version 7.10.1999]"
        cls.prettyname = "MS-DOS-7.10"
        cls.files = [
            ("io.sys", "8c586b1bf38fc2042f2383ca873283a466be2f44"),
            ("msdos.sys", "cd1e6103ce9cdebbc7a5611df13ff4fbd5e2159c"),
            ("command.com", "f6547d81e625a784633c059e536e90ee45532202"),
        ]
        cls.systype = SYSTYPE_MSDOS_NEW
        cls.autoexec = "autoemu.bat"
        cls.bootblocks = [
            ("boot-306-4-17.blk", "0f520de6e2a33ef8fd336b2844957689fc1060e9"),
            ("boot-615-4-17.blk", "5e49a8ee7747191d87a2214cc0281736262687b9"),
            ("boot-900-15-17.blk", "2c29d06909c7d5ca46a3ca26ddde9287a11ef315"),
        ]
        cls.images = [
            ("boot-floppy.img", ""),
        ]

        cls.actions = {
        }

        cls.setUpClassPost()

    def setUpDosAutoexec(self):
        # Use the (almost) standard shipped config
        contents = (self.topdir / "src" / "bindist" / self.autoexec).read_text()
        contents = re.sub(r"[Dd]:\\", r"c:\\", contents)
        self.mkfile(self.autoexec, contents, newline="\r\n")

    def setUpDosConfig(self):
        # Link back to std dosemu commands and scripts
        p = self.workdir / "dosemu"
        p.symlink_to(self.topdir / "commands" / "dosemu")

        # Use the (almost) standard shipped config
        contents = (self.topdir / "src" / "bindist" / "c" / self.confsys).read_text()
        contents = re.sub(r"[Dd]:\\", r"c:\\", contents)
        contents = re.sub(r"rem SWITCHES=/F", r"SWITCHES=/F", contents)
        self.mkfile(self.confsys, contents, newline="\r\n")

    def setUpDosVersion(self):
        self.mkfile("version.bat", "ver\r\nrem end\r\n")

        # Disable the logo here or we get blank screen
        self.mkfile("msdos.sys", """
[Options]
BootGUI=0
Logo=0
""", newline="\r\n")


class PPDOSGITTestCase(OurTestCase, unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        super(PPDOSGITTestCase, cls).setUpClass()
        cls.version = "FDPP kernel"
        cls.prettyname = "PP-DOS-GIT"
        cls.actions = {
            "test_floppy_img": UNSUPPORTED,
            "test_floppy_vfs": UNSUPPORTED,
        }

        # Use the default files that FDPP installed
        cls.tarfile = ""

        cls.systype = SYSTYPE_FDPP
        cls.autoexec = "fdppauto.bat"
        cls.confsys = "fdppconf.sys"

        cls.setUpClassPost()


if __name__ == '__main__':

    # Dynamically create libi86 tests
    libi86_create_items(OurTestCase)

    tests = [t[0] for t in
            inspect.getmembers(OurTestCase, predicate=inspect.isfunction)
            if t[0].startswith("test")]

    xtests = [t[0] for t in
            inspect.getmembers(OurTestCase, predicate=inspect.isfunction)
            if t[0].startswith("xtest")]

    cases = [c[0] for c in
            inspect.getmembers(modules[__name__], predicate=inspect.isclass)
            if issubclass(c[1], OurTestCase) and c[0] != "OurTestCase"]

    attrs = sorted(OurTestCase.attrs)

    def explode(n, attr=None):
        if n in tests:
            return [c + "." + n for c in cases]
        if n in xtests:
            return [c + "." + n for c in cases]
        if n in cases:
            if attr:
                return [n + "." + t[0] for t in
                    inspect.getmembers(OurTestCase, predicate=inspect.isfunction)
                    if hasattr(t[1], attr)]
            else:
                return [n,]
        p = n.split('.')
        if p and p[0] in cases and (p[1] in tests or p[1] in xtests):
            return [n,]
        return []

    if len(argv) > 1:
        if argv[1] == "--help":
            print(("Usage: %s [--help | --get-test-binaries | " +
                   "--list-attrs | --list-cases | --list-tests] | " +
                   "[--require-attr=STRING TestCase ...] | " +
                   "[TestCase[.testname] ...]") % argv[0])
            exit(0)
        elif argv[1] == "--get-test-binaries":
            get_test_binaries()
            exit(0)
        elif argv[1] == "--list-attrs":
            for a in attrs:
                print(str(a))
            exit(0)
        elif argv[1] == "--list-cases":
            for m in cases:
                print(str(m))
            exit(0)
        elif argv[1] == "--list-tests":
            for m in tests:
                print(str(m))
            exit(0)
        else:
            x = re.match(r"^--require-attr=(\S+).*$", argv[1])
            if x:
                attr = x.groups()[0]
                del argv[1]
            else:
                attr = None

            a = []
            for b in [explode(x, attr=attr) for x in argv[1:]]:
                a.extend(b)

            if not len(a):
                print("No tests found, was your testcase or testname incorrect? See --help")
                exit(1)
            argv = [argv[0],] + a
            main(argv)

    main()
