feat: runner v0.3 — parallel execution, skip snapshots for external

Add parallel test execution with worker pool (default: CPU count).
New --concurrency N option (--concurrency 1 for sequential).
Pre-warm shared fixtures before parallel run.
Skip snapshot update/compare for external (read-only) workspaces —
prevents accidental copying of large config dumps.
Fix normalizeUuids for cf-info/cf-validate (false→true).

Result: 283 tests, 329s→84s (4x speedup).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-03-28 16:47:22 +03:00
co-authored by Claude Opus 4.6
parent 9422c66df4
commit 4551525718
15 changed files with 294 additions and 115 deletions
+1 -1
View File
@@ -6,6 +6,6 @@
],
"snapshot": {
"root": "workDir",
"normalizeUuids": false
"normalizeUuids": true
}
}
@@ -1,34 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Configuration uuid="490dc2d2-5fc6-4378-80e3-e44e3e997a26">
<Configuration uuid="UUID-001">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
<xr:ObjectId>8613c9b5-714f-4db3-ba57-92944d83f3e6</xr:ObjectId>
<xr:ClassId>UUID-002</xr:ClassId>
<xr:ObjectId>UUID-003</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
<xr:ObjectId>52c8688f-9020-459e-b2ff-acf11197aa37</xr:ObjectId>
<xr:ClassId>UUID-004</xr:ClassId>
<xr:ObjectId>UUID-005</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
<xr:ObjectId>6302d246-62c8-4400-9fc2-16ec7bbb4c26</xr:ObjectId>
<xr:ClassId>UUID-006</xr:ClassId>
<xr:ObjectId>UUID-007</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
<xr:ObjectId>8d278abf-2004-4056-abeb-af728d4fa9a7</xr:ObjectId>
<xr:ClassId>UUID-008</xr:ClassId>
<xr:ObjectId>UUID-009</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
<xr:ObjectId>ae8c947e-0c04-49b2-b33a-a7914b52e5bd</xr:ObjectId>
<xr:ClassId>UUID-010</xr:ClassId>
<xr:ObjectId>UUID-011</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
<xr:ObjectId>686d04c2-0d6f-4a5c-ad74-db5a8c6381f5</xr:ObjectId>
<xr:ClassId>UUID-012</xr:ClassId>
<xr:ObjectId>UUID-013</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
<xr:ObjectId>dd62b6ff-7ce1-41eb-a111-48079672630c</xr:ObjectId>
<xr:ClassId>UUID-014</xr:ClassId>
<xr:ObjectId>UUID-015</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Language uuid="cbe0dc07-5b2c-4c15-ba81-0a080fcbc586">
<Language uuid="UUID-001">
<Properties>
<Name>Русский</Name>
<Synonym>
@@ -1,34 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Configuration uuid="490dc2d2-5fc6-4378-80e3-e44e3e997a26">
<Configuration uuid="UUID-001">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
<xr:ObjectId>8613c9b5-714f-4db3-ba57-92944d83f3e6</xr:ObjectId>
<xr:ClassId>UUID-002</xr:ClassId>
<xr:ObjectId>UUID-003</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
<xr:ObjectId>52c8688f-9020-459e-b2ff-acf11197aa37</xr:ObjectId>
<xr:ClassId>UUID-004</xr:ClassId>
<xr:ObjectId>UUID-005</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
<xr:ObjectId>6302d246-62c8-4400-9fc2-16ec7bbb4c26</xr:ObjectId>
<xr:ClassId>UUID-006</xr:ClassId>
<xr:ObjectId>UUID-007</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
<xr:ObjectId>8d278abf-2004-4056-abeb-af728d4fa9a7</xr:ObjectId>
<xr:ClassId>UUID-008</xr:ClassId>
<xr:ObjectId>UUID-009</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
<xr:ObjectId>ae8c947e-0c04-49b2-b33a-a7914b52e5bd</xr:ObjectId>
<xr:ClassId>UUID-010</xr:ClassId>
<xr:ObjectId>UUID-011</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
<xr:ObjectId>686d04c2-0d6f-4a5c-ad74-db5a8c6381f5</xr:ObjectId>
<xr:ClassId>UUID-012</xr:ClassId>
<xr:ObjectId>UUID-013</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
<xr:ObjectId>dd62b6ff-7ce1-41eb-a111-48079672630c</xr:ObjectId>
<xr:ClassId>UUID-014</xr:ClassId>
<xr:ObjectId>UUID-015</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Language uuid="cbe0dc07-5b2c-4c15-ba81-0a080fcbc586">
<Language uuid="UUID-001">
<Properties>
<Name>Русский</Name>
<Synonym>
@@ -1,34 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Configuration uuid="490dc2d2-5fc6-4378-80e3-e44e3e997a26">
<Configuration uuid="UUID-001">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
<xr:ObjectId>8613c9b5-714f-4db3-ba57-92944d83f3e6</xr:ObjectId>
<xr:ClassId>UUID-002</xr:ClassId>
<xr:ObjectId>UUID-003</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
<xr:ObjectId>52c8688f-9020-459e-b2ff-acf11197aa37</xr:ObjectId>
<xr:ClassId>UUID-004</xr:ClassId>
<xr:ObjectId>UUID-005</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
<xr:ObjectId>6302d246-62c8-4400-9fc2-16ec7bbb4c26</xr:ObjectId>
<xr:ClassId>UUID-006</xr:ClassId>
<xr:ObjectId>UUID-007</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
<xr:ObjectId>8d278abf-2004-4056-abeb-af728d4fa9a7</xr:ObjectId>
<xr:ClassId>UUID-008</xr:ClassId>
<xr:ObjectId>UUID-009</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
<xr:ObjectId>ae8c947e-0c04-49b2-b33a-a7914b52e5bd</xr:ObjectId>
<xr:ClassId>UUID-010</xr:ClassId>
<xr:ObjectId>UUID-011</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
<xr:ObjectId>686d04c2-0d6f-4a5c-ad74-db5a8c6381f5</xr:ObjectId>
<xr:ClassId>UUID-012</xr:ClassId>
<xr:ObjectId>UUID-013</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
<xr:ObjectId>dd62b6ff-7ce1-41eb-a111-48079672630c</xr:ObjectId>
<xr:ClassId>UUID-014</xr:ClassId>
<xr:ObjectId>UUID-015</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Language uuid="cbe0dc07-5b2c-4c15-ba81-0a080fcbc586">
<Language uuid="UUID-001">
<Properties>
<Name>Русский</Name>
<Synonym>
@@ -1,34 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Configuration uuid="490dc2d2-5fc6-4378-80e3-e44e3e997a26">
<Configuration uuid="UUID-001">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
<xr:ObjectId>8613c9b5-714f-4db3-ba57-92944d83f3e6</xr:ObjectId>
<xr:ClassId>UUID-002</xr:ClassId>
<xr:ObjectId>UUID-003</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
<xr:ObjectId>52c8688f-9020-459e-b2ff-acf11197aa37</xr:ObjectId>
<xr:ClassId>UUID-004</xr:ClassId>
<xr:ObjectId>UUID-005</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
<xr:ObjectId>6302d246-62c8-4400-9fc2-16ec7bbb4c26</xr:ObjectId>
<xr:ClassId>UUID-006</xr:ClassId>
<xr:ObjectId>UUID-007</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
<xr:ObjectId>8d278abf-2004-4056-abeb-af728d4fa9a7</xr:ObjectId>
<xr:ClassId>UUID-008</xr:ClassId>
<xr:ObjectId>UUID-009</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
<xr:ObjectId>ae8c947e-0c04-49b2-b33a-a7914b52e5bd</xr:ObjectId>
<xr:ClassId>UUID-010</xr:ClassId>
<xr:ObjectId>UUID-011</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
<xr:ObjectId>686d04c2-0d6f-4a5c-ad74-db5a8c6381f5</xr:ObjectId>
<xr:ClassId>UUID-012</xr:ClassId>
<xr:ObjectId>UUID-013</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
<xr:ObjectId>dd62b6ff-7ce1-41eb-a111-48079672630c</xr:ObjectId>
<xr:ClassId>UUID-014</xr:ClassId>
<xr:ObjectId>UUID-015</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Language uuid="cbe0dc07-5b2c-4c15-ba81-0a080fcbc586">
<Language uuid="UUID-001">
<Properties>
<Name>Русский</Name>
<Synonym>
+1 -1
View File
@@ -6,6 +6,6 @@
],
"snapshot": {
"root": "workDir",
"normalizeUuids": false
"normalizeUuids": true
}
}
@@ -1,34 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Configuration uuid="490dc2d2-5fc6-4378-80e3-e44e3e997a26">
<Configuration uuid="UUID-001">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
<xr:ObjectId>8613c9b5-714f-4db3-ba57-92944d83f3e6</xr:ObjectId>
<xr:ClassId>UUID-002</xr:ClassId>
<xr:ObjectId>UUID-003</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
<xr:ObjectId>52c8688f-9020-459e-b2ff-acf11197aa37</xr:ObjectId>
<xr:ClassId>UUID-004</xr:ClassId>
<xr:ObjectId>UUID-005</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
<xr:ObjectId>6302d246-62c8-4400-9fc2-16ec7bbb4c26</xr:ObjectId>
<xr:ClassId>UUID-006</xr:ClassId>
<xr:ObjectId>UUID-007</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
<xr:ObjectId>8d278abf-2004-4056-abeb-af728d4fa9a7</xr:ObjectId>
<xr:ClassId>UUID-008</xr:ClassId>
<xr:ObjectId>UUID-009</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
<xr:ObjectId>ae8c947e-0c04-49b2-b33a-a7914b52e5bd</xr:ObjectId>
<xr:ClassId>UUID-010</xr:ClassId>
<xr:ObjectId>UUID-011</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
<xr:ObjectId>686d04c2-0d6f-4a5c-ad74-db5a8c6381f5</xr:ObjectId>
<xr:ClassId>UUID-012</xr:ClassId>
<xr:ObjectId>UUID-013</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
<xr:ObjectId>dd62b6ff-7ce1-41eb-a111-48079672630c</xr:ObjectId>
<xr:ClassId>UUID-014</xr:ClassId>
<xr:ObjectId>UUID-015</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Language uuid="cbe0dc07-5b2c-4c15-ba81-0a080fcbc586">
<Language uuid="UUID-001">
<Properties>
<Name>Русский</Name>
<Synonym>
@@ -1,34 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Configuration uuid="490dc2d2-5fc6-4378-80e3-e44e3e997a26">
<Configuration uuid="UUID-001">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
<xr:ObjectId>8613c9b5-714f-4db3-ba57-92944d83f3e6</xr:ObjectId>
<xr:ClassId>UUID-002</xr:ClassId>
<xr:ObjectId>UUID-003</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
<xr:ObjectId>52c8688f-9020-459e-b2ff-acf11197aa37</xr:ObjectId>
<xr:ClassId>UUID-004</xr:ClassId>
<xr:ObjectId>UUID-005</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
<xr:ObjectId>6302d246-62c8-4400-9fc2-16ec7bbb4c26</xr:ObjectId>
<xr:ClassId>UUID-006</xr:ClassId>
<xr:ObjectId>UUID-007</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
<xr:ObjectId>8d278abf-2004-4056-abeb-af728d4fa9a7</xr:ObjectId>
<xr:ClassId>UUID-008</xr:ClassId>
<xr:ObjectId>UUID-009</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
<xr:ObjectId>ae8c947e-0c04-49b2-b33a-a7914b52e5bd</xr:ObjectId>
<xr:ClassId>UUID-010</xr:ClassId>
<xr:ObjectId>UUID-011</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
<xr:ObjectId>686d04c2-0d6f-4a5c-ad74-db5a8c6381f5</xr:ObjectId>
<xr:ClassId>UUID-012</xr:ClassId>
<xr:ObjectId>UUID-013</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
<xr:ObjectId>dd62b6ff-7ce1-41eb-a111-48079672630c</xr:ObjectId>
<xr:ClassId>UUID-014</xr:ClassId>
<xr:ObjectId>UUID-015</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Language uuid="cbe0dc07-5b2c-4c15-ba81-0a080fcbc586">
<Language uuid="UUID-001">
<Properties>
<Name>Русский</Name>
<Synonym>
+196 -17
View File
@@ -1,12 +1,12 @@
#!/usr/bin/env node
// skill-test-runner v0.2 — Snapshot-based regression tests for 1C skill scripts
// Usage: node tests/skills/runner.mjs [filter] [--update-snapshots] [--runtime python] [--json report.json]
// skill-test-runner v0.3 — Snapshot-based regression tests for 1C skill scripts
// Usage: node tests/skills/runner.mjs [filter] [--update-snapshots] [--runtime python] [--json report.json] [--concurrency N]
import { execFileSync } from 'child_process';
import { execFileSync, execFile } from 'child_process';
import { existsSync, mkdirSync, mkdtempSync, rmSync, readFileSync, writeFileSync,
readdirSync, statSync, cpSync, copyFileSync } from 'fs';
import { join, resolve, dirname, relative, basename, extname } from 'path';
import { tmpdir } from 'os';
import { tmpdir, cpus } from 'os';
// ─── Paths ──────────────────────────────────────────────────────────────────
@@ -19,7 +19,7 @@ const CACHE = resolve(ROOT, '.cache');
// ─── CLI args ───────────────────────────────────────────────────────────────
function parseArgs(argv) {
const args = { filter: null, updateSnapshots: false, runtime: 'powershell', jsonReport: null, verbose: false };
const args = { filter: null, updateSnapshots: false, runtime: 'powershell', jsonReport: null, verbose: false, concurrency: cpus().length };
const rest = argv.slice(2);
for (let i = 0; i < rest.length; i++) {
const a = rest[i];
@@ -27,6 +27,7 @@ function parseArgs(argv) {
if (a === '--runtime' && rest[i + 1]) { args.runtime = rest[++i]; continue; }
if (a === '--json' && rest[i + 1]) { args.jsonReport = rest[++i]; continue; }
if (a === '--verbose' || a === '-v') { args.verbose = true; continue; }
if (a === '--concurrency' && rest[i + 1]) { args.concurrency = parseInt(rest[++i], 10) || 1; continue; }
if (!a.startsWith('--') && !args.filter) { args.filter = a.replace(/\\/g, '/'); continue; }
}
return args;
@@ -154,6 +155,31 @@ function execSkillRaw(runtime, scriptPath, args, cwd) {
});
}
function execSkillAsync(runtime, scriptPath, args, cwd) {
return new Promise((resolve, reject) => {
const execCwd = cwd || REPO_ROOT;
const cmd = runtime === 'python'
? [process.env.PYTHON || 'python', [scriptPath, ...args]]
: ['powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', scriptPath, ...args]];
const child = execFile(cmd[0], cmd[1], {
encoding: 'utf8',
timeout: 60_000,
cwd: execCwd,
}, (error, stdout, stderr) => {
if (error) {
const err = new Error(error.message);
err.status = error.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER' ? 1 : (error.code ?? 1);
err.stdout = stdout || '';
err.stderr = stderr || '';
reject(err);
} else {
resolve(stdout);
}
});
});
}
// ─── Workspace ──────────────────────────────────────────────────────────────
function createWorkspace(fixturePath, readOnly) {
@@ -339,6 +365,119 @@ function updateSnapshot(workDir, snapshotDir, snapshotConfig) {
// ─── Run a single case ──────────────────────────────────────────────────────
async function runCaseAsync(testCase, opts) {
const { skillConfig, caseData, snapshotDir } = testCase;
const t0 = performance.now();
const setupName = caseData.setup || skillConfig.setup || 'none';
let workspace = null;
let workDir = null;
let inputFile = null;
try {
const skillCasesDir = join(CASES, testCase.skillDir);
const fixturePath = ensureSetup(setupName, opts.runtime, skillCasesDir);
if (fixturePath === SKIP) {
return { id: testCase.id, skill: testCase.skillDir, name: testCase.name, passed: true, skipped: true, errors: [], elapsed: '0.0s' };
}
const isExternal = typeof setupName === 'string' && setupName.startsWith('external:');
workspace = createWorkspace(fixturePath, isExternal);
workDir = workspace.path;
// Pre-run steps
if (caseData.preRun) {
for (const step of caseData.preRun) {
const preScript = resolveScript(step.script, opts.runtime);
const preArgs = [];
for (const [flag, value] of Object.entries(step.args || {})) {
preArgs.push(flag);
if (value === true || value === '') continue;
preArgs.push(String(value).replace('{workDir}', workDir).replace('{inputFile}', ''));
}
let preInputFile = null;
if (step.input) {
preInputFile = join(workDir, '__pre_input.json');
writeFileSync(preInputFile, JSON.stringify(step.input, null, 2), 'utf8');
for (let i = 0; i < preArgs.length; i++) {
if (preArgs[i] === '') preArgs[i] = preInputFile;
}
}
try {
const preCwd = step.cwd === '{workDir}' ? workDir : undefined;
await execSkillAsync(opts.runtime, preScript, preArgs, preCwd);
} catch (e) {
throw new Error(`preRun step "${step.script}" failed: ${e.stderr || e.message}`);
}
if (preInputFile && existsSync(preInputFile)) rmSync(preInputFile);
}
}
// Write input
if (caseData.input !== undefined) {
inputFile = join(workDir, '__input.json');
writeFileSync(inputFile, JSON.stringify(caseData.input, null, 2), 'utf8');
}
// Execute
const { scriptPath, args } = buildArgs(skillConfig, caseData, workDir, inputFile, opts.runtime);
let stdout = '', stderr = '', exitCode = 0;
try {
const execCwd = skillConfig.cwd === 'workDir' ? workDir : undefined;
stdout = await execSkillAsync(opts.runtime, scriptPath, args, execCwd);
} catch (e) {
exitCode = e.status ?? 1;
stdout = e.stdout || '';
stderr = e.stderr || '';
}
if (inputFile && existsSync(inputFile)) rmSync(inputFile);
// Assertions
const errors = [];
if (caseData.expectError) {
if (exitCode === 0) errors.push('Expected error (non-zero exit) but got exitCode=0');
if (typeof caseData.expectError === 'string' && !stderr.includes(caseData.expectError)) {
errors.push(`Expected stderr to contain "${caseData.expectError}", got: ${stderr.substring(0, 200)}`);
}
} else {
if (exitCode !== 0) {
errors.push(`exitCode=${exitCode}\nstdout: ${stdout.substring(0, 300)}\nstderr: ${stderr.substring(0, 300)}`);
}
if (caseData.expect?.files) {
for (const f of caseData.expect.files) {
if (!existsSync(join(workDir, f))) errors.push(`Expected file not found: ${f}`);
}
}
if (caseData.expect?.stdoutContains) {
if (!stdout.includes(caseData.expect.stdoutContains)) {
errors.push(`stdout does not contain "${caseData.expect.stdoutContains}"`);
}
}
if (errors.length === 0 && !caseData.expectError && !workspace.readOnly) {
const snapshotConfig = skillConfig.snapshot || {};
if (opts.updateSnapshots) {
updateSnapshot(workDir, snapshotDir, snapshotConfig);
} else {
const cmp = compareSnapshot(workDir, snapshotDir, snapshotConfig);
if (!cmp.match && cmp.diffs) {
for (const d of cmp.diffs) {
if (d.type === 'missing') errors.push(`Snapshot: file missing — ${d.file}`);
else errors.push(`Snapshot: ${d.file}:${d.line} differs\n expected: ${d.expected}\n actual: ${d.actual}`);
}
}
}
}
}
const elapsed = ((performance.now() - t0) / 1000).toFixed(1);
return { id: testCase.id, skill: testCase.skillDir, name: testCase.name, passed: errors.length === 0, errors, elapsed: `${elapsed}s`, snapshotUpdated: opts.updateSnapshots && !caseData.expectError && !workspace.readOnly };
} catch (e) {
const elapsed = ((performance.now() - t0) / 1000).toFixed(1);
return { id: testCase.id, skill: testCase.skillDir, name: testCase.name, passed: false, errors: [`Runner error: ${e.message}`], elapsed: `${elapsed}s` };
} finally {
if (workspace) cleanupWorkspace(workspace);
}
}
function runCase(testCase, opts) {
const { skillConfig, caseData, snapshotDir } = testCase;
const t0 = performance.now();
@@ -458,8 +597,8 @@ function runCase(testCase, opts) {
}
}
// Snapshot comparison
if (errors.length === 0 && !caseData.expectError) {
// Snapshot comparison (skip for external/read-only workspaces)
if (errors.length === 0 && !caseData.expectError && !workspace.readOnly) {
const snapshotConfig = skillConfig.snapshot || {};
if (opts.updateSnapshots) {
updateSnapshot(workDir, snapshotDir, snapshotConfig);
@@ -486,7 +625,7 @@ function runCase(testCase, opts) {
passed: errors.length === 0,
errors,
elapsed: `${elapsed}s`,
snapshotUpdated: opts.updateSnapshots && !caseData.expectError,
snapshotUpdated: opts.updateSnapshots && !caseData.expectError && !workspace.readOnly,
};
} catch (e) {
@@ -506,7 +645,7 @@ function runCase(testCase, opts) {
// ─── Reporter ───────────────────────────────────────────────────────────────
function printReport(results, opts) {
function printReport(results, opts, wallTime) {
const skipped = results.filter(r => r.skipped);
const passed = results.filter(r => r.passed && !r.skipped);
const failed = results.filter(r => !r.passed);
@@ -561,10 +700,11 @@ function printReport(results, opts) {
}
}
const totalTime = results.reduce((s, r) => s + parseFloat(r.elapsed), 0).toFixed(1);
const cpuTime = results.reduce((s, r) => s + parseFloat(r.elapsed), 0).toFixed(1);
console.log('');
const skippedStr = skipped.length > 0 ? ` | Skipped: ${skipped.length}` : '';
console.log(` Passed: ${passed.length} | Failed: ${failed.length}${skippedStr} | Total: ${results.length} | Time: ${totalTime}s`);
const timeStr = wallTime ? `${wallTime}s wall, ${cpuTime}s cpu` : `${cpuTime}s`;
console.log(` Passed: ${passed.length} | Failed: ${failed.length}${skippedStr} | Total: ${results.length} | Time: ${timeStr}`);
console.log('');
if (opts.jsonReport) {
@@ -589,9 +729,30 @@ function printReport(results, opts) {
return failed.length === 0;
}
// ─── Parallel pool ─────────────────────────────────────────────────────────
async function runPool(cases, opts) {
const results = new Array(cases.length);
let next = 0;
async function worker() {
while (next < cases.length) {
const idx = next++;
results[idx] = await runCaseAsync(cases[idx], opts);
}
}
const workers = [];
for (let i = 0; i < Math.min(opts.concurrency, cases.length); i++) {
workers.push(worker());
}
await Promise.all(workers);
return results;
}
// ─── Main ───────────────────────────────────────────────────────────────────
function main() {
async function main() {
const opts = parseArgs(process.argv);
const cases = discoverCases(opts.filter);
@@ -600,17 +761,35 @@ function main() {
process.exit(0);
}
console.log(`\nRunning ${cases.length} test(s)... [runtime: ${opts.runtime}]`);
const parallel = opts.concurrency > 1;
const modeStr = parallel ? `${opts.concurrency} workers` : 'sequential';
console.log(`\nRunning ${cases.length} test(s)... [runtime: ${opts.runtime}, ${modeStr}]`);
// Ensure cache dir exists
mkdirSync(CACHE, { recursive: true });
const results = [];
for (const tc of cases) {
results.push(runCase(tc, opts));
// Pre-warm shared fixtures before parallel run
const setups = new Set(cases.map(c => c.caseData.setup || c.skillConfig.setup || 'none'));
for (const setup of setups) {
if (setup === 'empty-config' || setup === 'base-config') {
try { ensureSetup(setup, opts.runtime, CASES); } catch {}
}
}
const allPassed = printReport(results, opts);
const wallStart = performance.now();
let results;
if (parallel) {
results = await runPool(cases, opts);
} else {
results = [];
for (const tc of cases) {
results.push(await runCaseAsync(tc, opts));
}
}
const wallTime = ((performance.now() - wallStart) / 1000).toFixed(1);
const allPassed = printReport(results, opts, wallTime);
process.exit(allPassed ? 0 : 1);
}