Record three user-approved voices and trace reused generation environments
This commit is contained in:
@@ -21,9 +21,14 @@
|
||||
"rms": 0.07999517023563385,
|
||||
"qualityWarnings": []
|
||||
},
|
||||
"review": "not_listened",
|
||||
"status": "draft",
|
||||
"processing": "polyphase resample to mono PCM16 44100 Hz; no speech trimming"
|
||||
"review": "user_approved",
|
||||
"status": "approved",
|
||||
"processing": "polyphase resample to mono PCM16 44100 Hz; no speech trimming",
|
||||
"reviewedBy": "user",
|
||||
"reviewedDate": "2026-10-05",
|
||||
"reviewTimezone": "Asia/Seoul",
|
||||
"reviewStatement": "세 파일 모두 괜찮습니다",
|
||||
"approvedSha256": "13a882ce1620140d447635e14f2d4758045d5e348960c17386df1c720050c100"
|
||||
},
|
||||
{
|
||||
"id": "count_02",
|
||||
@@ -210,9 +215,14 @@
|
||||
"rms": 0.11202728003263474,
|
||||
"qualityWarnings": []
|
||||
},
|
||||
"review": "not_listened",
|
||||
"status": "draft",
|
||||
"processing": "polyphase resample to mono PCM16 44100 Hz; no speech trimming"
|
||||
"review": "user_approved",
|
||||
"status": "approved",
|
||||
"processing": "polyphase resample to mono PCM16 44100 Hz; no speech trimming",
|
||||
"reviewedBy": "user",
|
||||
"reviewedDate": "2026-10-05",
|
||||
"reviewTimezone": "Asia/Seoul",
|
||||
"reviewStatement": "세 파일 모두 괜찮습니다",
|
||||
"approvedSha256": "62e15c9e38c36f4094a6c7e9c5148510141290b5f41db5aad9dc1d8f1a158594"
|
||||
},
|
||||
{
|
||||
"id": "guide_count",
|
||||
@@ -231,9 +241,14 @@
|
||||
"rms": 0.06778868287801743,
|
||||
"qualityWarnings": []
|
||||
},
|
||||
"review": "not_listened",
|
||||
"status": "draft",
|
||||
"processing": "polyphase resample to mono PCM16 44100 Hz; no speech trimming"
|
||||
"review": "user_approved",
|
||||
"status": "approved",
|
||||
"processing": "polyphase resample to mono PCM16 44100 Hz; no speech trimming",
|
||||
"reviewedBy": "user",
|
||||
"reviewedDate": "2026-10-05",
|
||||
"reviewTimezone": "Asia/Seoul",
|
||||
"reviewStatement": "세 파일 모두 괜찮습니다",
|
||||
"approvedSha256": "debe29b89f3e2856f2bf4c5e01707a5f0a0657e5768c03be385ec88fa2328b40"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"runId": "20261004T165608559600Z_21530dcf",
|
||||
"createdUtc": "2026-10-04T16:56:08.559600+00:00",
|
||||
"modelRevision": "0c0e3051f131929182e2c023b9537f8b1c68adfe",
|
||||
"catalogHash": "491c9cd0bb7068a47b33bdd0b19ece1ba634217ec0ed9cc3e2c5aa7bd3802755",
|
||||
"profileHash": "a5740d9db8a4aaebf08d0ac3b8f3225693f878aa2868143f01191a059e2680e8",
|
||||
"scope": "selected",
|
||||
"regenerateRequested": false,
|
||||
"requestedIds": [
|
||||
"count_01",
|
||||
"guide_collect",
|
||||
"guide_count"
|
||||
],
|
||||
"environment": {
|
||||
"python": "3.12.15 | packaged by Anaconda, Inc. | (main, Oct 1 2026, 20:14:50) [MSC v.1942 64 bit (AMD64)]",
|
||||
"packages": {
|
||||
"qwen-tts": "0.1.1",
|
||||
"torch": "2.11.0+cu128",
|
||||
"torchaudio": "2.11.0",
|
||||
"transformers": "4.57.3",
|
||||
"soundfile": "0.14.0",
|
||||
"huggingface-hub": "0.36.2"
|
||||
},
|
||||
"modelLoad": "not_needed",
|
||||
"requestedDevice": "cuda:0",
|
||||
"sourceGenerations": [
|
||||
{
|
||||
"runId": "20261004T161148012934Z_47f3a009",
|
||||
"manifest": "generated/20261004T161148012934Z_47f3a009/manifest.json",
|
||||
"manifestSha256": "302b162f50b541aca8f82461429a9d478cbd342afe062240010a4c80f9853be8",
|
||||
"environment": {
|
||||
"python": "3.12.15 | packaged by Anaconda, Inc. | (main, Oct 1 2026, 20:14:50) [MSC v.1942 64 bit (AMD64)]",
|
||||
"packages": {
|
||||
"qwen-tts": "0.1.1",
|
||||
"torch": "2.11.0+cu128",
|
||||
"torchaudio": "2.11.0",
|
||||
"transformers": "4.57.3",
|
||||
"soundfile": "0.14.0",
|
||||
"huggingface-hub": "0.36.2"
|
||||
},
|
||||
"device": "NVIDIA GeForce RTX 4080 SUPER",
|
||||
"cuda": "12.8",
|
||||
"cudaProbe": 140.0,
|
||||
"modelLoadSeconds": 3.2142242998816073,
|
||||
"modelPeakVramBytes": 4181138432,
|
||||
"modelDefaults": {
|
||||
"do_sample": true,
|
||||
"repetition_penalty": 1.05,
|
||||
"temperature": 0.9,
|
||||
"top_p": 1.0,
|
||||
"top_k": 50,
|
||||
"subtalker_dosample": true,
|
||||
"subtalker_temperature": 0.9,
|
||||
"subtalker_top_p": 1.0,
|
||||
"subtalker_top_k": 50,
|
||||
"max_new_tokens": 8192
|
||||
},
|
||||
"effectiveGenerationParameters": {
|
||||
"do_sample": true,
|
||||
"top_k": 50,
|
||||
"top_p": 1,
|
||||
"temperature": 0.9,
|
||||
"repetition_penalty": 1.05,
|
||||
"subtalker_dosample": true,
|
||||
"subtalker_top_k": 50,
|
||||
"subtalker_top_p": 1,
|
||||
"subtalker_temperature": 0.9,
|
||||
"max_new_tokens": 8192
|
||||
},
|
||||
"peakVramBytes": 4232247296
|
||||
}
|
||||
},
|
||||
{
|
||||
"runId": "20261004T161231511126Z_aa20f404",
|
||||
"manifest": "generated/20261004T161231511126Z_aa20f404/manifest.json",
|
||||
"manifestSha256": "a45ca839d8d826c1c5ca223c7b9aee9c8147cd475a27d8de33153ba8dac21422",
|
||||
"environment": {
|
||||
"python": "3.12.15 | packaged by Anaconda, Inc. | (main, Oct 1 2026, 20:14:50) [MSC v.1942 64 bit (AMD64)]",
|
||||
"packages": {
|
||||
"qwen-tts": "0.1.1",
|
||||
"torch": "2.11.0+cu128",
|
||||
"torchaudio": "2.11.0",
|
||||
"transformers": "4.57.3",
|
||||
"soundfile": "0.14.0",
|
||||
"huggingface-hub": "0.36.2"
|
||||
},
|
||||
"device": "NVIDIA GeForce RTX 4080 SUPER",
|
||||
"cuda": "12.8",
|
||||
"cudaProbe": 140.0,
|
||||
"modelLoadSeconds": 3.5885288000572473,
|
||||
"modelPeakVramBytes": 4181138432,
|
||||
"modelDefaults": {
|
||||
"do_sample": true,
|
||||
"repetition_penalty": 1.05,
|
||||
"temperature": 0.9,
|
||||
"top_p": 1.0,
|
||||
"top_k": 50,
|
||||
"subtalker_dosample": true,
|
||||
"subtalker_temperature": 0.9,
|
||||
"subtalker_top_p": 1.0,
|
||||
"subtalker_top_k": 50,
|
||||
"max_new_tokens": 8192
|
||||
},
|
||||
"effectiveGenerationParameters": {
|
||||
"do_sample": true,
|
||||
"top_k": 50,
|
||||
"top_p": 1,
|
||||
"temperature": 0.9,
|
||||
"repetition_penalty": 1.05,
|
||||
"subtalker_dosample": true,
|
||||
"subtalker_top_k": 50,
|
||||
"subtalker_top_p": 1,
|
||||
"subtalker_temperature": 0.9,
|
||||
"max_new_tokens": 8192
|
||||
},
|
||||
"peakVramBytes": 4386499584
|
||||
}
|
||||
}
|
||||
],
|
||||
"device": "NVIDIA GeForce RTX 4080 SUPER",
|
||||
"deviceProbe": "nvidia-smi; no model or CUDA inference executed"
|
||||
},
|
||||
"clips": [
|
||||
{
|
||||
"id": "count_01",
|
||||
"textHash": "4865b7d3cb44d273047c04c7f4ba1e2efdd2c489c365b9f5308f8e4c25c4ffe3",
|
||||
"settingsHash": "a5740d9db8a4aaebf08d0ac3b8f3225693f878aa2868143f01191a059e2680e8",
|
||||
"originalRunId": "20261004T161148012934Z_47f3a009",
|
||||
"file": "generated/20261004T161148012934Z_47f3a009/count_01.wav",
|
||||
"review": "not_listened",
|
||||
"sha256": "cb7f5e25d8ba3e726b9557597002aef72c35e51edd6012defcb327728c10021f",
|
||||
"sampleRate": 24000,
|
||||
"channels": 1,
|
||||
"frames": 13440,
|
||||
"duration": 0.56,
|
||||
"peak": 0.34375,
|
||||
"rms": 0.07997916638851166,
|
||||
"qualityWarnings": [],
|
||||
"result": "reused",
|
||||
"generationSeconds": 1.9430886001791805
|
||||
},
|
||||
{
|
||||
"id": "guide_collect",
|
||||
"textHash": "963c0253da8f4595da3ef968c576481f4590960d533b5059fcdfe8e791ee7a22",
|
||||
"settingsHash": "a5740d9db8a4aaebf08d0ac3b8f3225693f878aa2868143f01191a059e2680e8",
|
||||
"originalRunId": "20261004T161148012934Z_47f3a009",
|
||||
"file": "generated/20261004T161148012934Z_47f3a009/guide_collect.wav",
|
||||
"review": "not_listened",
|
||||
"sha256": "c772e4d9996b129eb4fdd59d90d03e93efa7ef434112e342be2e455d203878e6",
|
||||
"sampleRate": 24000,
|
||||
"channels": 1,
|
||||
"frames": 48000,
|
||||
"duration": 2.0,
|
||||
"peak": 0.60546875,
|
||||
"rms": 0.11194184422492981,
|
||||
"qualityWarnings": [],
|
||||
"result": "reused",
|
||||
"generationSeconds": 4.32505049998872
|
||||
},
|
||||
{
|
||||
"id": "guide_count",
|
||||
"textHash": "b1dc061607df3f064c4e2625adaf2579dabd9b15ef68bda16a3583035838e1a2",
|
||||
"settingsHash": "a5740d9db8a4aaebf08d0ac3b8f3225693f878aa2868143f01191a059e2680e8",
|
||||
"originalRunId": "20261004T161231511126Z_aa20f404",
|
||||
"file": "generated/20261004T161231511126Z_aa20f404/guide_count.wav",
|
||||
"review": "not_listened",
|
||||
"sha256": "63086b0a17769a6c1a8e270f75db08db604b5eeb430456042e168ce95d02babe",
|
||||
"sampleRate": 24000,
|
||||
"channels": 1,
|
||||
"frames": 86400,
|
||||
"duration": 3.6,
|
||||
"peak": 0.578125,
|
||||
"rms": 0.06774810701608658,
|
||||
"qualityWarnings": [],
|
||||
"result": "reused",
|
||||
"generationSeconds": 8.88178739999421
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
# 한국어 Qwen 음성 자산 기록
|
||||
|
||||
현재 상태: 로컬 GPU 음성 생성과 파일 검사를 완료했습니다. **발음·어절 잘림·음색 적합성은 아직 직접 듣고 검수하지 않았습니다.** 게임에 연결한 파일은 초안이며 최종 음성으로 승인한 파일이 아닙니다. 효과음과 배경음악은 사용자가 나중에 추가하기로 했습니다.
|
||||
현재 상태: 로컬 GPU 음성 생성과 파일 검사를 완료했습니다. **사용자가 2026-10-05 한국 시간에 count_01·guide_collect·guide_count 세 실제 게임 파일을 듣고 “세 파일 모두 괜찮습니다”라고 승인했습니다.** 해당 세 파일만 청취 승인 상태이며 나머지 숫자와 음성은 청취 미검수 초안입니다. 효과음과 배경음악은 사용자가 나중에 추가하기로 했습니다.
|
||||
|
||||
## 출처와 이용조건
|
||||
|
||||
@@ -27,12 +27,20 @@
|
||||
|
||||
| ID | 길이 | 상태 |
|
||||
| --- | ---: | --- |
|
||||
| count_01 | 0.56초 | T2 필수 초안, 청취 미검수 |
|
||||
| guide_collect | 2.00초 | T2 필수 초안, 청취 미검수 |
|
||||
| guide_count | 3.60초 | T2 필수 초안, 청취 미검수 |
|
||||
| count_01 | 0.56초 | T2 필수 음성, 2026-10-05 사용자 청취 승인 |
|
||||
| guide_collect | 2.00초 | T2 필수 음성, 2026-10-05 사용자 청취 승인 |
|
||||
| guide_count | 3.60초 | T2 필수 음성, 2026-10-05 사용자 청취 승인 |
|
||||
| count_02,03,04,06,07,08,09,10 | 0.48~1.44초 | 이후 계수용 초안, 청취 미검수 |
|
||||
| count_05 | 9.12초 | 긴 공백과 후반 소리의 이상 후보로 게임 연결에서 제외 |
|
||||
|
||||
승인 대상은 아래 실제 게임 WAV의 SHA-256입니다. 파일과 selection 이력의 편집본 해시가 일치함을 대조한 뒤 검수자·날짜·사용자 발언·승인 해시를 selection에 기록했습니다. 이 승인은 다른 숫자나 다른 대본·프로필·파일로 확대하지 않습니다.
|
||||
|
||||
| ID | 승인 SHA-256 |
|
||||
| --- | --- |
|
||||
| count_01 | `13a882ce1620140d447635e14f2d4758045d5e348960c17386df1c720050c100` |
|
||||
| guide_collect | `62e15c9e38c36f4094a6c7e9c5148510141290b5f41db5aad9dc1d8f1a158594` |
|
||||
| guide_count | `debe29b89f3e2856f2bf4c5e01707a5f0a0657e5768c03be385ec88fa2328b40` |
|
||||
|
||||
count_05의 전체 원본과 편집 후보는 원인 확인을 위해 보존했습니다. 숫자 한 단어가 2초보다 길면 초안 내보내기를 차단합니다. 이 기준은 검수 필요 신호이며 자동 발음 검사가 아닙니다. 같은 프로필로 특정 클립을 다시 만들 때는 `--ids count_05 --regenerate`를 사용하며 이전 회차를 보존합니다.
|
||||
|
||||
동일 프로필·seed 42로 count_05만 다시 생성한 회차 `20261004T162201751189Z_a400364e`에서도 9.12초 출력이 재현되었습니다. 실제 `cuda:0` 모델 로딩 2.974초, 추론 22.271초, 최대 GPU 할당 4,386,499,584바이트였습니다. 생성 자체는 성공했지만 정상 초안으로 채택하지 않았고 두 회차의 원본을 모두 보존했습니다. 파일 길이만으로 발음 합격을 선언하지 않으며, 이후 직접 청취한 뒤 같은 모델·목소리 범위에서 지시문·생성 설정을 검토해야 합니다.
|
||||
@@ -48,10 +56,12 @@ python tools/audio/generate_voice.py --catalog SourceAssets/Audio/Voice/voice-ca
|
||||
python tools/audio/generate_voice.py --catalog SourceAssets/Audio/Voice/voice-catalog.json --profile SourceAssets/Audio/Voice/qwen-profile.json --validate-only --manifest SourceAssets/Audio/Voice/generated/20261004T161231511126Z_aa20f404/manifest.json
|
||||
```
|
||||
|
||||
초기 RED는 생성기 모듈이 없어 종료 코드 1이었습니다. 최종 GREEN에서는 동작 검증 테스트 10개가 종료 코드 0으로 통과했습니다. 잘못된 카탈로그, 현재 대본·프로필과 부분 검사 범위, 실패하거나 비정상적인 WAV, 누락·손상 WAV, 대본·설정 변경, 해시 변조, 이전 원본 보존, 리샘플링, 다른 게임 파일 덮어쓰기 거부, 긴 숫자 출력 차단, 특정 클립만 새 회차로 생성하는 동작을 검사합니다.
|
||||
초기 RED는 생성기 모듈이 없어 종료 코드 1이었습니다. 최종 GREEN에서는 동작 검증 테스트 11개가 종료 코드 0으로 통과했습니다. 잘못된 카탈로그, 현재 대본·프로필과 부분 검사 범위, 실패하거나 비정상적인 WAV, 누락·손상 WAV, 대본·설정 변경, 해시 변조, 이전 원본 보존, 리샘플링, 다른 게임 파일 덮어쓰기 거부, 긴 숫자 출력 차단, 특정 클립만 새 회차로 생성하는 동작, 전부 재사용할 때 모델 로딩 없이 현재 패키지·장치와 원본 생성 환경을 연결하는 동작을 검사합니다.
|
||||
|
||||
첫 표본 생성·확대 생성·검사 전용 명령은 각각 종료 코드 0이었습니다. `--validate-only`는 모델을 로딩하거나 다운로드하지 않습니다. ID를 생략하면 현재 T2 필수 세 클립 전체를 검사하고, `--ids`를 지정하면 선택 범위를 결과에 기록합니다. 생성한 열두 클립을 모두 지정한 파일·이력 검사는 종료 코드 0이었지만 count_05의 별도 음질 문제는 합격으로 처리하지 않았습니다. 잘못된 ID `missing`은 해당 ID를 포함한 오류와 종료 코드 1을 반환했습니다. 마지막 `pip check`는 종료 코드 0으로 충돌이 없었습니다.
|
||||
|
||||
실제 후속 로그는 구현 worktree의 `artifacts/verification/FirstSlice/Voice/` 아래 `python-tests.log`, `validate-all-generated.log`, `validate-invalid-id.log`입니다. 산출물은 Git에서 제외하고 원본 manifest와 선택 파일 해시는 추적합니다.
|
||||
|
||||
Unity `.meta`는 통합 후 실제 Editor import로 생성해야 하며 직접 작성하지 않았습니다. 남은 검수는 선택한 각 발화를 듣고 검수자·날짜·승인 해시를 기록하는 작업, count_05 및 이후 안내 음성 검수, Android 빌드의 음성 큐·믹스 재생 확인입니다.
|
||||
전부 재사용하는 실제 CLI 검사 회차 `20261004T165608559600Z_21530dcf`는 세 필수 클립을 재사용하고 종료 코드 0으로 통과했습니다. 새 manifest에 현재 Python·패키지 버전, `nvidia-smi`로 확인한 RTX 4080 SUPER 장치, `modelLoad=not_needed`, 서로 다른 두 원본 생성 회차의 manifest 경로·SHA-256·실제 생성 환경을 기록했습니다. 원본 manifest 해시도 대조했습니다. 모델 로딩·재추론은 하지 않았으며 기존 승인 파일과 기본 프로필을 변경하지 않았습니다. 해당 실행 로그는 `reuse-environment.log`입니다.
|
||||
|
||||
Unity `.meta`는 통합 후 실제 Editor import로 생성해야 하며 직접 작성하지 않았습니다. 남은 검수는 승인된 세 파일을 제외한 발화의 청취·승인 해시 기록, count_05 및 이후 안내 음성 검수, Android 빌드의 음성 큐·믹스 재생 확인입니다.
|
||||
|
||||
@@ -6,6 +6,7 @@ import importlib.metadata
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
@@ -147,6 +148,27 @@ def reusable_entries(root, catalog, profile):
|
||||
return result
|
||||
|
||||
|
||||
def reuse_environment(profile, entries, root):
|
||||
environment = {'python': sys.version, 'packages': {name: importlib.metadata.version(name) for name in
|
||||
('qwen-tts', 'torch', 'torchaudio', 'transformers', 'soundfile', 'huggingface-hub')},
|
||||
'modelLoad': 'not_needed', 'requestedDevice': profile['device'], 'sourceGenerations': []}
|
||||
try:
|
||||
probe = subprocess.run(['nvidia-smi', '-i', '0', '--query-gpu=name', '--format=csv,noheader'],
|
||||
check=True, capture_output=True, text=True, timeout=5)
|
||||
environment['device'] = probe.stdout.strip()
|
||||
environment['deviceProbe'] = 'nvidia-smi; no model or CUDA inference executed'
|
||||
except (OSError, subprocess.SubprocessError) as error:
|
||||
environment['deviceProbeError'] = str(error)
|
||||
for run_id in sorted({entry['originalRunId'] for entry in entries.values()}):
|
||||
manifest_path = Path(root) / 'generated' / run_id / 'manifest.json'
|
||||
source = load_json(manifest_path)
|
||||
environment['sourceGenerations'].append({'runId': run_id,
|
||||
'manifest': manifest_path.relative_to(root).as_posix(),
|
||||
'manifestSha256': file_hash(manifest_path),
|
||||
'environment': source['environment']})
|
||||
return environment
|
||||
|
||||
|
||||
def generate(catalog, profile, root, ids=None, synthesize=None, environment=None, regenerate=False):
|
||||
validate_inputs(catalog, profile)
|
||||
selected = select_clips(catalog, ids)
|
||||
@@ -252,7 +274,11 @@ def main():
|
||||
else:
|
||||
selected = select_clips(catalog, ids)
|
||||
reusable = reusable_entries(root, catalog, profile)
|
||||
synthesize, environment = (None, {'modelLoad': 'not_needed'}) if not args.regenerate and all(c['id'] in reusable for c in selected) else load_synthesizer(profile)
|
||||
if not args.regenerate and all(c['id'] in reusable for c in selected):
|
||||
synthesize = None
|
||||
environment = reuse_environment(profile, {c['id']: reusable[c['id']] for c in selected}, root)
|
||||
else:
|
||||
synthesize, environment = load_synthesizer(profile)
|
||||
manifest = generate(catalog, profile, root, ids, synthesize, environment, args.regenerate)
|
||||
print('Manifest: ' + str(root / 'generated' / manifest['runId'] / 'manifest.json'))
|
||||
errors = validate_manifest(manifest, catalog, profile, root, ids)
|
||||
|
||||
@@ -3,6 +3,7 @@ import importlib.util
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import numpy as np
|
||||
import soundfile as sf
|
||||
@@ -134,6 +135,23 @@ class VoiceHistoryTests(unittest.TestCase):
|
||||
self.assertEqual(original, (self.root / first['clips'][0]['file']).read_bytes())
|
||||
self.assertEqual(['count_01', 'guide_collect', 'count_01'], self.calls)
|
||||
|
||||
def test_reuse_environment_records_packages_device_and_original_run_without_model_load(self):
|
||||
first = self.run_generation()
|
||||
entries = voice.reusable_entries(self.root, self.catalog, self.profile)
|
||||
with mock.patch.object(voice.importlib.metadata, 'version', return_value='test-version'), \
|
||||
mock.patch.object(voice.subprocess, 'run', return_value=mock.Mock(stdout='Test GPU\n')):
|
||||
environment = voice.reuse_environment(self.profile, entries, self.root)
|
||||
second = voice.generate(self.catalog, self.profile, self.root, environment=environment)
|
||||
self.assertEqual(['reused', 'reused'], [c['result'] for c in second['clips']])
|
||||
self.assertEqual(['count_01', 'guide_collect'], self.calls)
|
||||
self.assertEqual('not_needed', second['environment']['modelLoad'])
|
||||
self.assertEqual('Test GPU', second['environment']['device'])
|
||||
self.assertEqual('test-version', second['environment']['packages']['qwen-tts'])
|
||||
source = second['environment']['sourceGenerations'][0]
|
||||
self.assertEqual(first['runId'], source['runId'])
|
||||
self.assertEqual(voice.file_hash(self.root / source['manifest']), source['manifestSha256'])
|
||||
self.assertEqual({'test': True}, source['environment'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user