mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-18 13:53:01 +08:00
test: expand Drupal corpus proofs and native package regression
This commit is contained in:
@@ -7,6 +7,8 @@ on:
|
||||
branches: [feature/extensions-author-kit-20260922]
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'extensions/drupal/**'
|
||||
- 'scripts/build-extensions.mjs'
|
||||
- '__tests__/**'
|
||||
- 'scripts/validation/**'
|
||||
- 'package*.json'
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
{
|
||||
"pathauto": {
|
||||
"flows": [
|
||||
[
|
||||
"/admin/config/search/path/settings",
|
||||
"pathauto.routing.yml",
|
||||
"buildForm",
|
||||
"src/Form/PathautoSettingsForm.php",
|
||||
"Drupal route handler",
|
||||
"/admin/config/search/path/settings PathautoSettingsForm.buildForm"
|
||||
],
|
||||
[
|
||||
"/admin/config/search/path/update_bulk",
|
||||
"pathauto.routing.yml",
|
||||
"buildForm",
|
||||
"src/Form/PathautoBulkUpdateForm.php",
|
||||
"Drupal route handler",
|
||||
"/admin/config/search/path/update_bulk PathautoBulkUpdateForm.buildForm"
|
||||
],
|
||||
[
|
||||
"getPunctuationCharacters",
|
||||
"src/AliasCleaner.php",
|
||||
"pathautoPunctuationCharsAlter",
|
||||
"tests/modules/pathauto_custom_punctuation_test/src/Hook/PathautoCustomPunctuationTestHooks.php",
|
||||
"Drupal hook pathauto_punctuation_chars_alter",
|
||||
"AliasCleaner.getPunctuationCharacters PathautoCustomPunctuationTestHooks.pathautoPunctuationCharsAlter"
|
||||
]
|
||||
],
|
||||
"extra": [
|
||||
[
|
||||
"PathautoGenerator",
|
||||
"src/PathautoGenerator.php",
|
||||
"AliasCleaner",
|
||||
"src/AliasCleaner.php",
|
||||
"Drupal service injection"
|
||||
],
|
||||
[
|
||||
"pathauto_update_8100",
|
||||
"pathauto.install",
|
||||
"AliasTypeManager",
|
||||
"src/AliasTypeManager.php",
|
||||
"Drupal service lookup"
|
||||
],
|
||||
[
|
||||
"service:pathauto.alias_cleaner",
|
||||
"pathauto.services.yml",
|
||||
"AliasCleaner",
|
||||
"src/AliasCleaner.php",
|
||||
"Drupal service implementation"
|
||||
],
|
||||
[
|
||||
"PathautoGenerator",
|
||||
"src/PathautoGenerator.php",
|
||||
"AliasUniquifier",
|
||||
"src/AliasUniquifier.php",
|
||||
"Drupal service injection"
|
||||
],
|
||||
[
|
||||
"Action:pathauto_update_alias",
|
||||
"src/Plugin/Action/UpdateAction.php",
|
||||
"UpdateAction",
|
||||
"src/Plugin/Action/UpdateAction.php",
|
||||
"Drupal plugin implementation"
|
||||
],
|
||||
[
|
||||
"getPunctuationCharacters",
|
||||
"src/AliasCleaner.php",
|
||||
"pathauto_custom_punctuation_test_pathauto_punctuation_chars_alter",
|
||||
"tests/modules/pathauto_custom_punctuation_test/pathauto_custom_punctuation_test.module",
|
||||
"Drupal hook pathauto_punctuation_chars_alter"
|
||||
]
|
||||
],
|
||||
"negative": [
|
||||
[
|
||||
"/admin/config/search/path/patterns/add",
|
||||
"pathauto.routing.yml",
|
||||
"Drupal route handler",
|
||||
"_entity_form"
|
||||
],
|
||||
[
|
||||
"buildForm",
|
||||
"src/Form/PathautoBulkUpdateForm.php",
|
||||
"Drupal plugin construction",
|
||||
"createInstance($id)"
|
||||
]
|
||||
]
|
||||
},
|
||||
"commerce": {
|
||||
"flows": [
|
||||
[
|
||||
"/cart",
|
||||
"modules/cart/commerce_cart.routing.yml",
|
||||
"cartPage",
|
||||
"modules/cart/src/Controller/CartController.php",
|
||||
"Drupal route handler",
|
||||
"/cart CartController.cartPage"
|
||||
],
|
||||
[
|
||||
"/checkout/{commerce_order}/{step}",
|
||||
"modules/checkout/commerce_checkout.routing.yml",
|
||||
"formPage",
|
||||
"modules/checkout/src/Controller/CheckoutController.php",
|
||||
"Drupal route handler",
|
||||
"/checkout/{commerce_order}/{step} CheckoutController.formPage"
|
||||
],
|
||||
[
|
||||
"/admin/commerce/orders/add",
|
||||
"modules/order/commerce_order.routing.yml",
|
||||
"buildForm",
|
||||
"modules/order/src/Form/OrderAddForm.php",
|
||||
"Drupal route handler",
|
||||
"/admin/commerce/orders/add OrderAddForm.buildForm"
|
||||
]
|
||||
],
|
||||
"extra": [
|
||||
[
|
||||
"CurrentCountry",
|
||||
"src/CurrentCountry.php",
|
||||
"ChainCountryResolver",
|
||||
"src/Resolver/ChainCountryResolver.php",
|
||||
"Drupal service injection"
|
||||
],
|
||||
[
|
||||
"Block:commerce_cart",
|
||||
"modules/cart/src/Plugin/Block/CartBlock.php",
|
||||
"CartBlock",
|
||||
"modules/cart/src/Plugin/Block/CartBlock.php",
|
||||
"Drupal plugin implementation"
|
||||
],
|
||||
[
|
||||
"commerce_cart_cron",
|
||||
"modules/cart/commerce_cart.module",
|
||||
"Cron",
|
||||
"modules/cart/src/Cron.php",
|
||||
"Drupal service lookup"
|
||||
],
|
||||
[
|
||||
"CurrentLocale",
|
||||
"src/CurrentLocale.php",
|
||||
"ChainLocaleResolver",
|
||||
"src/Resolver/ChainLocaleResolver.php",
|
||||
"Drupal service injection"
|
||||
],
|
||||
[
|
||||
"QueueWorker:commerce_cart_expiration",
|
||||
"modules/cart/src/Plugin/QueueWorker/CartExpiration.php",
|
||||
"CartExpiration",
|
||||
"modules/cart/src/Plugin/QueueWorker/CartExpiration.php",
|
||||
"Drupal plugin implementation"
|
||||
],
|
||||
[
|
||||
"addOrderItem",
|
||||
"modules/cart/src/CartManager.php",
|
||||
"displayAddToCartMessage",
|
||||
"modules/cart/src/EventSubscriber/CartEventSubscriber.php",
|
||||
"Drupal event commerce_cart.entity.add"
|
||||
]
|
||||
],
|
||||
"negative": [
|
||||
[
|
||||
"/admin/commerce/config/products",
|
||||
"modules/product/commerce_product.routing.yml",
|
||||
"Drupal route handler",
|
||||
"SystemController::systemAdminMenuBlockPage"
|
||||
]
|
||||
]
|
||||
},
|
||||
"drupal": {
|
||||
"flows": [
|
||||
[
|
||||
"setAccount",
|
||||
"core/lib/Drupal/Core/Session/AccountProxy.php",
|
||||
"setDefaultTimeZone",
|
||||
"core/modules/system/src/TimeZoneResolver.php",
|
||||
"Drupal event account.set",
|
||||
"AccountProxy.setAccount TimeZoneResolver.setDefaultTimeZone"
|
||||
],
|
||||
[
|
||||
"filterResponse",
|
||||
"core/modules/big_pipe/src/Render/BigPipe.php",
|
||||
"onResponse",
|
||||
"core/lib/Drupal/Core/EventSubscriber/ActiveLinkResponseFilter.php",
|
||||
"Drupal event Symfony\\Component\\HttpKernel\\KernelEvents::RESPONSE",
|
||||
"BigPipe.filterResponse ActiveLinkResponseFilter.onResponse"
|
||||
],
|
||||
[
|
||||
"finish",
|
||||
"core/lib/Drupal/Core/Config/ConfigImporter.php",
|
||||
"onConfigImporterImport",
|
||||
"core/lib/Drupal/Core/EventSubscriber/ConfigSnapshotSubscriber.php",
|
||||
"Drupal event config.importer.import",
|
||||
"ConfigImporter.finish ConfigSnapshotSubscriber.onConfigImporterImport"
|
||||
]
|
||||
],
|
||||
"extra": [
|
||||
[
|
||||
"/admin/modules",
|
||||
"core/modules/system/system.routing.yml",
|
||||
"buildForm",
|
||||
"core/modules/system/src/Form/ModulesListForm.php",
|
||||
"Drupal route handler"
|
||||
],
|
||||
[
|
||||
"service:renderer",
|
||||
"core/core.services.yml",
|
||||
"Renderer",
|
||||
"core/lib/Drupal/Core/Render/Renderer.php",
|
||||
"Drupal service implementation"
|
||||
],
|
||||
[
|
||||
"ConfigActionManager",
|
||||
"core/lib/Drupal/Core/Config/Action/ConfigActionManager.php",
|
||||
"ConfigManager",
|
||||
"core/lib/Drupal/Core/Config/ConfigManager.php",
|
||||
"Drupal service injection"
|
||||
],
|
||||
[
|
||||
"_batch_page",
|
||||
"core/includes/batch.inc",
|
||||
"BatchStorage",
|
||||
"core/lib/Drupal/Core/Batch/BatchStorage.php",
|
||||
"Drupal service lookup"
|
||||
],
|
||||
[
|
||||
"_filter_url",
|
||||
"core/modules/filter/filter.module",
|
||||
"FilterUrl",
|
||||
"core/modules/filter/src/Plugin/Filter/FilterUrl.php",
|
||||
"Drupal plugin construction"
|
||||
],
|
||||
[
|
||||
"Filter:filter_url",
|
||||
"core/modules/filter/src/Plugin/Filter/FilterUrl.php",
|
||||
"FilterUrl",
|
||||
"core/modules/filter/src/Plugin/Filter/FilterUrl.php",
|
||||
"Drupal plugin implementation"
|
||||
],
|
||||
[
|
||||
"drupal_flush_all_caches",
|
||||
"core/includes/common.inc",
|
||||
"cacheFlush",
|
||||
"core/modules/locale/src/Hook/LocaleHooks.php",
|
||||
"Drupal hook cache_flush"
|
||||
]
|
||||
],
|
||||
"negative": [
|
||||
[
|
||||
"invokeAllDeprecated",
|
||||
"core/lib/Drupal/Core/Extension/ModuleHandler.php",
|
||||
"Drupal hook ",
|
||||
"invokeAll($hook"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Three separately receipted phases so a task interruption cannot erase progress.
|
||||
const fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict'),crypto=require('node:crypto');
|
||||
const {DatabaseSync}=require('node:sqlite'); const {CodeGraph}=require('../../dist');
|
||||
const [project,phase,output]=process.argv.slice(2);fs.mkdirSync(path.dirname(output),{recursive:true});
|
||||
const files=['review_convergence.php','review_convergence.routing.yml'];
|
||||
function snapshot(){const db=new DatabaseSync(path.join(project,'.codegraph/codegraph.db'),{readOnly:true});try{
|
||||
const nodes=db.prepare('SELECT * FROM nodes ORDER BY id').all().map(({updated_at,...n})=>n);
|
||||
const edges=db.prepare('SELECT source,target,kind,metadata,line,col,provenance FROM edges ORDER BY source,target,kind,line,col,metadata').all();
|
||||
return {hash:crypto.createHash('sha256').update(JSON.stringify({nodes,edges})).digest('hex'),nodes,edges};
|
||||
}finally{db.close();}}
|
||||
(async()=>{const record=fs.existsSync(output)?JSON.parse(fs.readFileSync(output)):{project,phases:[]};let g;
|
||||
try{
|
||||
if(phase==='add'){
|
||||
assert.equal(record.phases.length,0);record.original=snapshot().hash;
|
||||
for(const file of files)assert.ok(!fs.existsSync(path.join(project,file)));
|
||||
fs.writeFileSync(path.join(project,files[0]),`<?php
|
||||
namespace Drupal\\review;
|
||||
class ReviewSubscriber { public static function getSubscribedEvents() { return ['review.convergence' => 'onReview']; } public function onReview($event) {} }
|
||||
class ReviewDispatch { public function send($dispatcher,$event) { $dispatcher->dispatch('review.convergence', $event); } }
|
||||
`);
|
||||
fs.writeFileSync(path.join(project,files[1]),"review.route:\n path: '/__review_unknown'\n defaults:\n _controller: '\\Missing\\Unknown::missing'\n");
|
||||
}else assert.ok(['rebuild','remove'].includes(phase)&&record.incremental);
|
||||
if(phase==='remove'){for(const file of files)fs.unlinkSync(path.join(project,file));}
|
||||
const start=performance.now();g=await CodeGraph.open(project);
|
||||
const result=phase==='rebuild'?await g.refreshPluginIndex():await g.sync({paths:files});
|
||||
assert.ok(result?.success!==false&&!result?.filesErrored);g.close();g=null;
|
||||
const state=snapshot();
|
||||
if(phase==='add'){
|
||||
const route=state.nodes.find(n=>n.name==='/__review_unknown');assert.ok(route);assert.ok(!state.edges.some(e=>e.source===route.id));
|
||||
const send=state.nodes.find(n=>n.name==='send'&&n.file_path===files[0]),receive=state.nodes.find(n=>n.name==='onReview'&&n.file_path===files[0]);assert.ok(send&&receive);
|
||||
assert.equal(state.edges.filter(e=>e.source===send.id&&e.target===receive.id&&JSON.parse(e.metadata||'{}').label==='Drupal event review.convergence').length,1);
|
||||
record.incremental=state.hash;
|
||||
}else assert.equal(state.hash,phase==='rebuild'?record.incremental:record.original,'Incremental/full convergence');
|
||||
record.phases.push({phase,hash:state.hash,wallMs:performance.now()-start});record.success=record.phases.length===3;
|
||||
}catch(e){record.failure=String(e.stack);throw e;}finally{g?.close();fs.writeFileSync(output,JSON.stringify(record,null,2));}
|
||||
})().catch(e=>{console.error(e);process.exitCode=1;});
|
||||
@@ -0,0 +1,28 @@
|
||||
// Read-only source-grounded probes over an already indexed disposable corpus.
|
||||
const fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict'),crypto=require('node:crypto');
|
||||
const {DatabaseSync}=require('node:sqlite');
|
||||
const {CodeGraph}=require('../../dist');const {ToolHandler}=require('../../dist/mcp/tools');
|
||||
const [name,project,output]=process.argv.slice(2),cases=require('./drupal-review-cases.json')[name];
|
||||
const db=new DatabaseSync(path.join(project,'.codegraph/codegraph.db'),{readOnly:true});
|
||||
const hash=b=>crypto.createHash('sha256').update(b).digest('hex');
|
||||
const record={name,project,positive:[],negative:[],probes:[],success:false};
|
||||
function evidence(file,line){const bytes=fs.readFileSync(path.join(project,file));const lines=bytes.toString().split('\n');assert.ok(line>=1&&line<=lines.length);return {file,line,sha256:hash(bytes),excerpt:lines.slice(Math.max(0,line-3),line+3).join('\n')};}
|
||||
(async()=>{try{
|
||||
const edges=db.prepare("SELECT s.name source,s.file_path sourceFile,s.start_line sourceLine,t.name target,t.file_path targetFile,t.start_line targetLine,e.source sourceId,e.target targetId,e.kind,e.line,e.metadata FROM edges e JOIN nodes s ON e.source=s.id JOIN nodes t ON e.target=t.id WHERE json_extract(e.metadata,'$.synthesizedBy')='drupal'").all();
|
||||
const seen=new Set();for(const e of edges){const key=[e.sourceId,e.targetId,e.kind,e.line].join('|');assert.ok(!seen.has(key),'Duplicate extension edge '+key);seen.add(key);}
|
||||
for(const [source,sf,target,tf,label]of [...cases.flows,...cases.extra]){
|
||||
const matches=edges.filter(e=>e.source===source&&e.sourceFile===sf&&e.target===target&&e.targetFile===tf&&JSON.parse(e.metadata).label===label);assert.equal(matches.length,1,'Expected unique grounded edge '+JSON.stringify([source,sf,target,tf,label]));
|
||||
const e=matches[0],m=JSON.parse(e.metadata),at=/^(.*):(\d+)$/.exec(m.registeredAt);assert.ok(at);
|
||||
record.positive.push({source,target,label,kind:e.kind,from:evidence(sf,e.sourceLine),to:evidence(tf,e.targetLine),wiring:evidence(at[1],Number(at[2]))});
|
||||
}
|
||||
for(const [source,file,labelPrefix,text]of cases.negative){
|
||||
assert.ok(fs.readFileSync(path.join(project,file),'utf8').includes(text),'Negative has source evidence');
|
||||
assert.ok(db.prepare('SELECT 1 FROM nodes WHERE name=? AND file_path=?').get(source,file),'Negative source node exists');
|
||||
assert.equal(edges.filter(e=>e.source===source&&e.sourceFile===file&&JSON.parse(e.metadata).label.startsWith(labelPrefix)).length,0,'Unsupported/dynamic target must stay unresolved');
|
||||
record.negative.push({source,file,labelPrefix,text,sha256:hash(fs.readFileSync(path.join(project,file)))});
|
||||
}
|
||||
const graph=await CodeGraph.open(project);try{
|
||||
for(const c of cases.flows){const response=await new ToolHandler(graph).execute('codegraph_explore',{query:c[5]});const text=response.content.map(v=>v.text||'').join('\n');assert.ok(!response.isError&&text.includes(c[4]),'Expected labelled flow');record.probes.push({query:c[5],text});}
|
||||
}finally{graph.close();}
|
||||
record.edgeCount=edges.length;record.noDuplicateKeys=true;record.success=true;
|
||||
}finally{db.close();fs.mkdirSync(path.dirname(output),{recursive:true});fs.writeFileSync(output,JSON.stringify(record,null,2));}})().catch(e=>{console.error(e);process.exitCode=1;});
|
||||
@@ -21,7 +21,7 @@ const playwright = process.env.PLAYWRIGHT_MODULE || path.join(root, '.qa/browser
|
||||
env.PLAYWRIGHT_MODULE = playwright;
|
||||
env.CHROME_PATH = process.env.CHROME_PATH || require(playwright).chromium.executablePath();
|
||||
async function run(name, command, args, timeout = 360000, overrides = {}) {
|
||||
if (metadata.scope === 'marketplace' && !['build','marketplace-build','focused','registry-storage','external-author','compatibility','browser'].includes(name)) {
|
||||
if (metadata.scope === 'marketplace' && !['build','marketplace-build','drupal-package','drupal-accuracy','focused','registry-storage','external-author','compatibility','browser'].includes(name)) {
|
||||
metadata.notRun.push({ name, reason: 'Unchanged core lifecycle; retain prior native evidence instead of rerunning' }); save(); return true;
|
||||
}
|
||||
const step = { name, command: [command, ...args], cwd: root, revision: version, overrides, started: new Date().toISOString(), status: 'running' };
|
||||
@@ -45,6 +45,8 @@ async function run(name, command, args, timeout = 360000, overrides = {}) {
|
||||
const built = await run('build', ...npmCommand(['run', 'build']));
|
||||
if (built) {
|
||||
await run('marketplace-build', ...npmCommand(['run', 'build', '--prefix', 'marketplace']));
|
||||
await run('drupal-package', process.execPath, ['scripts/build-extensions.mjs']);
|
||||
await run('drupal-accuracy', process.execPath, ['--test', 'scripts/validation/drupal-accuracy.test.cjs', 'scripts/validation/drupal-negative.test.cjs']);
|
||||
await run('focused', process.execPath, ['node_modules/vitest/vitest.mjs', 'run',
|
||||
'__tests__/marketplace-storage.test.ts', '__tests__/foundation.test.ts', '__tests__/extension-trust.test.ts', '__tests__/extension-releases.test.ts', '__tests__/extension-marketplace.test.ts', '__tests__/extension-author.test.ts', '__tests__/plugins.test.ts', '__tests__/extension-explore.test.ts',
|
||||
'__tests__/db-reopen-on-replace.test.ts', '__tests__/status-json.test.ts', '__tests__/sync.test.ts', '__tests__/concurrent-locking.test.ts',
|
||||
|
||||
Reference in New Issue
Block a user