test: validate historian TLS migration against isolated source
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate the future patch in archives, using published 1.0.0 or exact HG source project references."""
|
||||
import argparse, datetime, hashlib, io, json, os, shutil, subprocess, tarfile, urllib.request
|
||||
import xml.etree.ElementTree as ET
|
||||
from pathlib import Path
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--consumer-root', type=Path, required=True)
|
||||
parser.add_argument('--hg-root', type=Path, required=True)
|
||||
parser.add_argument('--hg-commit', default='e4321915ebc5e0704a68ecad21b85c1cc1c80d6b')
|
||||
parser.add_argument('--output', type=Path, required=True)
|
||||
a = parser.parse_args(); root = a.consumer_root.resolve(); out = a.output.resolve()
|
||||
if out.is_relative_to(root) or out.is_relative_to(a.hg_root.resolve()):
|
||||
raise ValueError('Use an output directory outside both checkouts so archived HG does not inherit consumer central-package settings.')
|
||||
out.mkdir(parents=True, exist_ok=False)
|
||||
patch = Path(__file__).with_name('consumer-1.0.0.patch').resolve()
|
||||
project = Path('tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests.csproj')
|
||||
sha = subprocess.check_output(['git','-C',str(root),'rev-parse','HEAD'],text=True).strip()
|
||||
hg_sha = subprocess.check_output(['git','-C',str(a.hg_root),'rev-parse',a.hg_commit],text=True).strip()
|
||||
def archive(repo, rev, target):
|
||||
target.mkdir()
|
||||
data = subprocess.check_output(['git','-C',str(repo),'archive',rev])
|
||||
with tarfile.open(fileobj=io.BytesIO(data)) as tar: tar.extractall(target, filter='data')
|
||||
|
||||
versions = {}
|
||||
for name in ['client','contracts']:
|
||||
url = 'https://gitea.dohertylan.com/api/packages/dohertj2/nuget/package/zb.mom.ww.historiangateway.' + name + '/index.json'
|
||||
versions[name] = json.load(urllib.request.urlopen(url,timeout=20))['versions']
|
||||
published = all('1.0.0' in v for v in versions.values())
|
||||
metadata = {'utc':datetime.datetime.now(datetime.timezone.utc).isoformat(),'consumer_commit':sha,'hg_snapshot':hg_sha,
|
||||
'mode':'published-1.0.0' if published else 'unreleased-source-project-reference',
|
||||
'feed_versions':versions,'patch_sha256':hashlib.sha256(patch.read_bytes()).hexdigest(),
|
||||
'published_package_verification':published,'normal_package_cache_modified':False}
|
||||
(out/'metadata.json').write_text(json.dumps(metadata,indent=2))
|
||||
consumer = out/'consumer'; archive(root,sha,consumer)
|
||||
subprocess.run(['git','init','-q',str(consumer)],check=True)
|
||||
subprocess.run(['git','-C',str(consumer),'apply',str(patch)],check=True)
|
||||
if not published:
|
||||
hg = out/'historiangw-source'; archive(a.hg_root,hg_sha,hg)
|
||||
adapter = consumer/'src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.csproj'
|
||||
tree = ET.parse(adapter)
|
||||
removed = 0
|
||||
for group in tree.getroot().findall('ItemGroup'):
|
||||
for item in list(group):
|
||||
if item.tag=='PackageReference' and item.get('Include') in ['ZB.MOM.WW.HistorianGateway.Client','ZB.MOM.WW.HistorianGateway.Contracts']:
|
||||
group.remove(item);removed += 1
|
||||
assert removed==2
|
||||
group = ET.SubElement(tree.getroot(),'ItemGroup')
|
||||
for relative in ['clients/dotnet/ZB.MOM.WW.HistorianGateway.Client/ZB.MOM.WW.HistorianGateway.Client.csproj',
|
||||
'src/ZB.MOM.WW.HistorianGateway.Contracts/ZB.MOM.WW.HistorianGateway.Contracts.csproj']:
|
||||
ET.SubElement(group,'ProjectReference',Include=str(hg/relative))
|
||||
tree.write(adapter,encoding='unicode')
|
||||
metadata['temporary_override']='Only archived Gateway csproj: two package references replaced by exact archived HG project references; no package packed or published.'
|
||||
# Copy cached dependencies independently, preferably APFS copy-on-write, into the isolated package root.
|
||||
cache = out/'packages'; cache.mkdir()
|
||||
assets = json.loads((root/project.parent/'obj/project.assets.json').read_text())
|
||||
normal = Path(next(iter(assets['packageFolders'])))
|
||||
for library in assets['libraries'].values():
|
||||
if library.get('type')!='package':continue
|
||||
relative = library['path']; source=normal/relative; target=cache/relative
|
||||
target.parent.mkdir(parents=True,exist_ok=True)
|
||||
if target.exists():continue
|
||||
r=subprocess.run(['cp','-cR',str(source),str(target)],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
|
||||
if r.returncode: shutil.copytree(source,target)
|
||||
config = ET.parse(root/'NuGet.config')
|
||||
for source in config.getroot().find('packageSources'):
|
||||
if source.get('key')=='local-mxgw':
|
||||
local = root/'nuget-packages'
|
||||
if not local.exists():local=out/'empty-local';local.mkdir()
|
||||
source.set('value',str(local))
|
||||
config.write(out/'NuGet.config',encoding='unicode')
|
||||
env=dict(os.environ,NUGET_PACKAGES=str(cache),NUGET_HTTP_CACHE_PATH=str(out/'http-cache'),DOTNET_CLI_HOME=str(out/'dotnet-home'),
|
||||
DOTNET_SKIP_FIRST_TIME_EXPERIENCE='1',DOTNET_GENERATE_ASPNET_CERTIFICATE='false',DOTNET_CLI_TELEMETRY_OPTOUT='1')
|
||||
commands=[]
|
||||
def run(args,name):
|
||||
with (out/name).open('w') as log:r=subprocess.run(args,cwd=consumer,env=env,stdout=log,stderr=subprocess.STDOUT)
|
||||
commands.append({'argv':args,'exit':r.returncode,'log':name})
|
||||
(out/'commands.json').write_text(json.dumps(commands,indent=2));print(name,'exit',r.returncode,flush=True)
|
||||
return r.returncode
|
||||
restore = run(['dotnet','restore',str(project),'--configfile',str(out/'NuGet.config'),'--packages',str(cache)],'restore.log')
|
||||
metadata['restore_exit']=restore
|
||||
if restore:
|
||||
(out/'metadata.json').write_text(json.dumps(metadata,indent=2));raise SystemExit(restore)
|
||||
results=out/'test-results'
|
||||
test = run(['dotnet','test',str(project),'-c','Release','--no-restore','--logger','trx','--results-directory',str(results)],'test.log')
|
||||
guard_env=env.copy();guard_env['MIN_EXECUTED']='1'
|
||||
with (out/'guard.log').open('w') as log:
|
||||
guard=subprocess.run(['bash',str(consumer/'scripts/ci/assert-not-all-skipped.sh'),*[str(p) for p in results.glob('*.trx')]],env=guard_env,stdout=log,stderr=subprocess.STDOUT)
|
||||
metadata.update({'test_exit':test,'guard_exit':guard.returncode})
|
||||
ns={'t':'http://microsoft.com/schemas/VisualStudio/TeamTest/2010'}
|
||||
metadata['results']=[]
|
||||
for trx in results.glob('*.trx'):
|
||||
xml=ET.parse(trx);metadata['results'].append(xml.find('.//t:Counters',ns).attrib)
|
||||
metadata['tls_cases']=[{'name':r.get('testName'),'outcome':r.get('outcome'),'output':'\n'.join(p.text or '' for p in r.findall('.//t:StdOut',ns)),
|
||||
'error':r.findtext('.//t:Message','',ns)} for r in xml.findall('.//t:UnitTestResult',ns) if 'HistorianGatewayTlsPolicyWireTests' in r.get('testName','')]
|
||||
(out/'metadata.json').write_text(json.dumps(metadata,indent=2));print(json.dumps(metadata,indent=2),flush=True)
|
||||
raise SystemExit(test if test else guard.returncode)
|
||||
Reference in New Issue
Block a user