File: mergeall-products/unzipped/test/test-path-normalization-3.3/test-path-normalization-walks/linux/_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')