File: mergeall-products/unzipped/test/test-path-normalization-3.3/test-path-normalization-walks/_TEST.py

"""
------------------------------------------------------------------
[3.3] Create paths with mixed decomposed and composed Unicode
filenames, and test compares, syncs, and deltas in Mergeall 3.3.

Run in this script's folder with no command-line arguments; 
test folders are auto created in '.' (the CWD).  This script
file's content is the same in all platform subfolders.

Results for each platform are in the platform subfolders' 
_TEST-output.txt (console) and LOGS/* (run steps).  The most 
important output may be in LOGS/*deltas-apply*.txt for each 
platform, as these files show path normalization in action. 

Coding: an Oct-22 takeoff on ../../test-normalization-3.3's 
test script.  Coded here portably to run on for Unix (macOS), 
Windows, Linux, and Android (Termux).  Uses manual decoding, 
else open('name') may depend on platform policies, cut/paste,
and keyboard input for Unicode text.

Test data: this fudges the skewed Unicode-variant state that path
normalization fixes, by renaming TO's path components after a 
unique TO path has been saved in __added__.txt by a deltas create.  

How Tested:

Results here vary with the debug1/debug2 switches in Mergeall's
fixunicodedups.matchUnicodePathnames().  See that function's 
docstring for switch usage.  This reflects platform policies:

- Because macOS and Android (shared and app-specific storage) 
  auto-normalize paths with Unicode variants to match, they will
  happily sync the skewed test data here without any manual 
  normalization.  Set debug1 to True to force a normalization 
  loop (and output "--Path okay" messages), or set both switches
  to True to force the loop and component normalization (and 
  output "--Unicode morphed" messages).

- Because Windows, Linux, and Android (app-private storage) do not
  auto-normalize paths, they will run a manual normalization loop
  for the test data here, without setting Mergeall's switches to 
  True.  Windows and Linux seem the primary beneficiary of manual 
  path normalization; Android app-private is subpar for content
  storage, as it's accessible to just one app.

For runs of this script captured in this folder: 
- debug1/2 were True/False  on macOS to force loop only
- debug1/2 were True/True   on Android shared storage for loop+mods
- debug1/2 were False/False on Windows and Linux: not required
- debug1/2 were False/False on Android app-specific to skip loop
- debug1/2 were False/False on Android app-private: not required

Comment: NO platforms should auto-normalize filenames; this can
lead to duplicates in syncs that aren't as forgiving as Mergeall.
------------------------------------------------------------------
"""

# before runs in Windows console: set PYTHONIOENCODING=utf8

import os, glob, shutil, time, sys
from os import system as cmd    

# Windows, Android, macOS, Linux
if sys.platform.startswith('win'):
    mergeall33 = r'C:\Users\lutz\Desktop\temp\mergeall-oct0922'
    python3 = 'py -3'

elif any(key.startswith("ANDROID") for key in os.environ.keys()):
    mergeall33 = r'/sdcard/work/mergeall-oct0922'
    python3 = 'python3'

elif sys.platform.startswith('darwin'):
    mergeall33 = '/Users/me/MY-STUFF/Code/mergeall'    # 3.3 dev tree
    python3 = 'python3'

elif sys.platform.startswith('linux'):
    mergeall33 = '/home/me/Desktop/temp/mergeall-oct0922'
    python3 = 'python3'

else:
    assert False, 'Not testing here'

if os.path.exists('LOGS'):
    shutil.rmtree('LOGS')
os.mkdir('LOGS')


# for alt-form filenames: {ndc, nfd} Liñux
nfc = b'Li\xc3\xb1ux'.decode('utf-8')          # decoded str, composed (NFC)
nfd = b'Lin\xcc\x83ux'.decode('utf-8')         # decoded str, decomposed (NFD)


def populate():
    #-------------------------------------------------------
    # make mixed-unicode-name folders+files test data, anew
    #-------------------------------------------------------

    def mkfile(name, content):                 # name is NFC|NFD, decoded
        f = open(name, 'w', encoding='utf8')   # encoding of content, not name
        f.write(content)
        f.close()

    path1 = [nfc+'1', 'aaa', nfd+'2', 'bbb', nfd+'3', 'ccc', nfc+'4', 'ddd']
    path2 = [nfd+'1', 'aaa', nfd+'2', 'bbb', nfc+'3', 'ccc', nfc+'4', 'ddd']

    file1 = nfc
    file2 = nfd
 
    # in portable relative paths
    for (folder, path, file) in [('FROM', path1, file1), ('TO', path2, file2)]:
        if os.path.exists(folder): 
            shutil.rmtree(folder)
       
        fullpath = os.path.join(folder, *path)
        os.makedirs(fullpath)
        
        for newfile in (file, 'plain.txt'):
            fullfile = os.path.join(fullpath, newfile)
            raw = newfile.encode('utf-8')
            mkfile(fullfile, content='%s, %s' % (newfile, raw))


def dump(label, deltas=False):
    #-------------------------------------------------------
    # display test data's state to verify populate and syncs
    #-------------------------------------------------------

    def storedpath(root):
        "get the full path stored under root, skip __bkp__"
        for (dirhere, subshere, fileshere) in os.walk(root):
            path = dirhere
            if '__bkp__' in subshere: subshere.remove('__bkp__')
        return path

    print('\n' + label.upper())

    print('\n<decoded paths>')
    for folder in ('FROM', 'TO'):
        path = storedpath(folder)
        print('  ' + path if folder == 'TO' else path)

    print('\n<encoded paths>')
    for folder in ('FROM', 'TO'):
        path = storedpath(folder)
        print([part.encode('utf8') for part in path.split(os.sep)])

    print('\n<files>')
    for folder in ('FROM', 'TO'):
        path = storedpath(folder)
        for file in os.listdir(path):
            rawfile = file.encode('utf8')
            filepath = os.path.join(path, file)
            print('  ' + filepath if folder == 'TO' else filepath, 
                  '-->', rawfile, 
                  '==>', open(filepath, 'rb').read())

    if deltas:
        print('\n<__added__.txt>')
        added = open('DELTAS' + os.sep + '__added__.txt', 'rb').read()
        print(added.decode('utf8'), end='')
        print(added)


def compare(label):
    #-------------------------------------------------------
    # compare FROM and TO with mergeall (modtimes+structure)
    # and diffall (bytewise); don't use -quiet here or in 
    # deltas tests, so see Unicode normalization messages;
    #-------------------------------------------------------
 
    cmd(python3 + ' %s/mergeall.py FROM TO -report -skipcruft'
             ' > LOGS/%s-mergeall-f-t.txt' % (mergeall33, label))

    cmd(python3 + ' %s/diffall.py FROM TO -skipcruft'
             ' > LOGS/%s-diffall-f-t.txt' % (mergeall33, label))

    # TMI...
    #cmd(python3 + ' %s/mergeall.py TO FROM -report -skipcruft'
    #         ' > LOGS/%s-mergeall-t-f.txt' % (mergeall33, label))
    #
    #cmd(python3 + ' %s/diffall.py TO FROM -skipcruft'
    #         ' > LOGS/%s-diffall-t-f.txt' % (mergeall33, label))


def modify(folder='FROM'):
    #-------------------------------------------------------
    # modify data in FROM, to create diffs with TO (to sync)
    #-------------------------------------------------------

    time.sleep(3)                                 # evade FAT 2 seconds tolerance range

    for (dirhere, subshere, fileshere) in os.walk(folder):
        for file in fileshere:
            filepath = os.path.join(dirhere, file)
            if file == 'plain.txt':
                # cmd('echo xxx > ' + filepath)
                new = open(filepath, 'wb')
                new.write(b'modified...')         # change modtime+size
                new.close()                       # flush now to save
            else:
                # cmd('rm ' + filepath)
                os.remove(filepath)               # make unique TO => __added__.txt entry


def sync():
    #-------------------------------------------------------
    # sync trees with mergeall directly/immediately
    #-------------------------------------------------------

    cmd(python3 + ' %s/mergeall.py FROM TO -auto -skipcruft'
             ' > LOGS/3-sync-mergeall-33.txt' % mergeall33)


def make_to_path_differ(dodump=False):
    #-------------------------------------------------------
    # rename all "Liñux?" folders in TO's path to force TO's 
    # path to differ with the prior TO path in __added__.txt, 
    # at least on platforms that do not auto-normalize; 
    #
    # this suffices to skew __added__.txt and TO for path
    # existence tests on Windows and Linux, but not for 
    # Android or macOS, where filenames are auto-normalized,
    # and code debug flags can force loop tests (see How 
    # Tested in the top-of-file docstring);
    #
    # on Windows and Linux, all "Liñux?" are normalized as
    # expected; the final path name is not (it's not renamed
    # because it has no digit suffix), and skipping renames 
    # for some folders also skips normalization for them;
    #
    # this creates the state addressed by Mergeall path
    # normalization; it's complex to generate artificially,
    # and has yet to be spotted in the wild (so far...);
    #-------------------------------------------------------
 
    join = os.path.join
    for (dir, subs, files) in os.walk('TO', topdown=False):
        for name in subs + files:
            if name[:-1] == nfc:
                newname = nfd + name[-1]
                os.rename(join(dir, name), join(dir, newname))
            elif name[:-1] == nfd:
                newname = nfc + name[-1]
                os.rename(join(dir, name), join(dir, newname))
            else:
                pass # print('skip', name)

    if dodump: dump('post make to path differ')


def deltas_create(num):
    #-------------------------------------------------------
    # save FROM deltas, including unique TO in __added__.txt
    #-------------------------------------------------------

    cmd(python3 + ' %s/deltas.py DELTAS FROM TO -skipcruft'
             ' > LOGS/%s-deltas-create-33.txt' % (mergeall33, num))


def deltas_apply(num):
    #-------------------------------------------------------
    # apply FROM deltas to TO, with relative paths
    #-------------------------------------------------------

    cmd(python3 + ' %s/mergeall.py DELTAS TO -restore -auto -backup -skipcruft'   # -quiet
            ' > LOGS/%s-deltas-apply-33.txt' % (mergeall33, num))


def deltas_apply_abspath(num):
    #-------------------------------------------------------
    # apply FROM deltas to TO, with absolute paths
    #-------------------------------------------------------
 
    absde = os.path.abspath('DELTAS')
    absto = os.path.abspath('TO')

    cmd(python3 + ' %s/mergeall.py %s %s -restore -auto -backup -skipcruft'   # -quiet
            ' > LOGS/%s-deltas-apply-33.txt' % (mergeall33, absde, absto, num))


if __name__ == '__main__':
    #-------------------------------------------------------
    # go
    #-------------------------------------------------------

    print('Starting')
    populate()
    dump('initial populate')
    compare('1-initial-state')

    input('\nPress enter to mod and sync')
    modify('FROM')
    compare('2-post-modify')
    sync()
    compare('4-post-sync')
    dump('post mod and sync')

    input('\nPress enter to run deltas sync')
    populate()
    modify('FROM')
    deltas_create(5)
    make_to_path_differ()
    deltas_apply(6)
    compare('7-post-deltas-apply')
    dump('post deltas sync', deltas=True)

    input('\nPress enter to run deltas abspath sync')
    populate()
    modify('FROM')
    deltas_create(8)
    make_to_path_differ()
    deltas_apply_abspath(9)
    compare('A-post-deltas-apply')
    dump('post deltas abspath sync', deltas=True)
    print('\nBye')



[Home page] Books Code Blog Python Author Train Find ©M.Lutz