mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 12:33:10 +00:00
[ci] Fix auto label platform restructure false positive (#16734)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
8
.github/scripts/auto-label-pr/detectors.js
vendored
8
.github/scripts/auto-label-pr/detectors.js
vendored
@@ -107,6 +107,8 @@ async function detectNewPlatforms(github, context, prFiles, apiData) {
|
|||||||
/^esphome\/components\/([^\/]+)\/([^\/]+)\/__init__\.py$/,
|
/^esphome\/components\/([^\/]+)\/([^\/]+)\/__init__\.py$/,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const removedFiles = new Set(prFiles.filter(file => file.status === 'removed').map(file => file.filename));
|
||||||
|
|
||||||
for (const file of addedFiles) {
|
for (const file of addedFiles) {
|
||||||
for (const re of platformPathPatterns) {
|
for (const re of platformPathPatterns) {
|
||||||
const match = file.match(re);
|
const match = file.match(re);
|
||||||
@@ -114,6 +116,12 @@ async function detectNewPlatforms(github, context, prFiles, apiData) {
|
|||||||
const platform = match[2];
|
const platform = match[2];
|
||||||
if (!apiData.platformComponents.includes(platform)) break;
|
if (!apiData.platformComponents.includes(platform)) break;
|
||||||
|
|
||||||
|
// Skip if this is a restructure between flat and subdirectory forms (either direction):
|
||||||
|
// <component>/<platform>.py <-> <component>/<platform>/__init__.py
|
||||||
|
const flatEquivalent = `esphome/components/${match[1]}/${platform}.py`;
|
||||||
|
const subdirEquivalent = `esphome/components/${match[1]}/${platform}/__init__.py`;
|
||||||
|
if (removedFiles.has(flatEquivalent) || removedFiles.has(subdirEquivalent)) break;
|
||||||
|
|
||||||
labels.add('new-platform');
|
labels.add('new-platform');
|
||||||
const content = await fetchPrFileContent(github, context, file);
|
const content = await fetchPrFileContent(github, context, file);
|
||||||
if (content === null) {
|
if (content === null) {
|
||||||
|
|||||||
7
.github/scripts/auto-label-pr/package.json
vendored
Normal file
7
.github/scripts/auto-label-pr/package.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "auto-label-pr",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"test": "node --test tests/*.test.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
147
.github/scripts/auto-label-pr/tests/detectors.test.js
vendored
Normal file
147
.github/scripts/auto-label-pr/tests/detectors.test.js
vendored
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
const { describe, it } = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const { detectNewPlatforms, detectNewComponents } = require('../detectors');
|
||||||
|
|
||||||
|
// Minimal GitHub API mock — only repos.getContent is called by detectNewPlatforms/detectNewComponents
|
||||||
|
// to check for CONFIG_SCHEMA in newly added files.
|
||||||
|
function makeGithub(content = '') {
|
||||||
|
return {
|
||||||
|
rest: {
|
||||||
|
repos: {
|
||||||
|
getContent: async () => ({
|
||||||
|
data: { content: Buffer.from(content).toString('base64') }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONTEXT = {
|
||||||
|
repo: { owner: 'esphome', repo: 'esphome' },
|
||||||
|
payload: { pull_request: { head: { sha: 'abc123' }, base: { ref: 'dev' } } }
|
||||||
|
};
|
||||||
|
|
||||||
|
const API_DATA = {
|
||||||
|
targetPlatforms: ['esp32', 'esp8266', 'rp2040'],
|
||||||
|
platformComponents: ['cover', 'sensor', 'binary_sensor', 'switch', 'light', 'fan', 'climate', 'valve']
|
||||||
|
};
|
||||||
|
|
||||||
|
const WITH_SCHEMA = 'CONFIG_SCHEMA = cv.Schema({})';
|
||||||
|
const WITHOUT_SCHEMA = 'CODEOWNERS = ["@esphome/core"]';
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// detectNewPlatforms
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
describe('detectNewPlatforms', () => {
|
||||||
|
describe('restructure detection (no false positives)', () => {
|
||||||
|
it('flat .py -> subdir __init__.py is not a new platform', async () => {
|
||||||
|
const prFiles = [
|
||||||
|
{ filename: 'esphome/components/endstop/cover.py', status: 'removed' },
|
||||||
|
{ filename: 'esphome/components/endstop/cover/__init__.py', status: 'added' },
|
||||||
|
];
|
||||||
|
const result = await detectNewPlatforms(makeGithub(WITH_SCHEMA), CONTEXT, prFiles, API_DATA);
|
||||||
|
assert.equal(result.labels.size, 0);
|
||||||
|
assert.equal(result.hasYamlLoadable, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('subdir __init__.py -> flat .py is not a new platform', async () => {
|
||||||
|
const prFiles = [
|
||||||
|
{ filename: 'esphome/components/endstop/cover/__init__.py', status: 'removed' },
|
||||||
|
{ filename: 'esphome/components/endstop/cover.py', status: 'added' },
|
||||||
|
];
|
||||||
|
const result = await detectNewPlatforms(makeGithub(WITH_SCHEMA), CONTEXT, prFiles, API_DATA);
|
||||||
|
assert.equal(result.labels.size, 0);
|
||||||
|
assert.equal(result.hasYamlLoadable, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('genuine new platforms', () => {
|
||||||
|
it('new subdir platform with CONFIG_SCHEMA sets new-platform and hasYamlLoadable', async () => {
|
||||||
|
const prFiles = [
|
||||||
|
{ filename: 'esphome/components/my_sensor/cover/__init__.py', status: 'added' },
|
||||||
|
];
|
||||||
|
const result = await detectNewPlatforms(makeGithub(WITH_SCHEMA), CONTEXT, prFiles, API_DATA);
|
||||||
|
assert.ok(result.labels.has('new-platform'));
|
||||||
|
assert.equal(result.hasYamlLoadable, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('new flat platform with CONFIG_SCHEMA sets new-platform and hasYamlLoadable', async () => {
|
||||||
|
const prFiles = [
|
||||||
|
{ filename: 'esphome/components/my_sensor/cover.py', status: 'added' },
|
||||||
|
];
|
||||||
|
const result = await detectNewPlatforms(makeGithub(WITH_SCHEMA), CONTEXT, prFiles, API_DATA);
|
||||||
|
assert.ok(result.labels.has('new-platform'));
|
||||||
|
assert.equal(result.hasYamlLoadable, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('new platform without CONFIG_SCHEMA sets new-platform but not hasYamlLoadable', async () => {
|
||||||
|
const prFiles = [
|
||||||
|
{ filename: 'esphome/components/my_sensor/cover.py', status: 'added' },
|
||||||
|
];
|
||||||
|
const result = await detectNewPlatforms(makeGithub(WITHOUT_SCHEMA), CONTEXT, prFiles, API_DATA);
|
||||||
|
assert.ok(result.labels.has('new-platform'));
|
||||||
|
assert.equal(result.hasYamlLoadable, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('non-platform file addition produces no labels', async () => {
|
||||||
|
const prFiles = [
|
||||||
|
{ filename: 'esphome/components/my_sensor/sensor.py', status: 'added' },
|
||||||
|
];
|
||||||
|
// Override platformComponents so 'sensor' is not a recognized platform -> no label expected.
|
||||||
|
const nonPlatformApiData = { ...API_DATA, platformComponents: ['cover'] };
|
||||||
|
const result = await detectNewPlatforms(makeGithub(WITH_SCHEMA), CONTEXT, prFiles, nonPlatformApiData);
|
||||||
|
assert.equal(result.labels.size, 0);
|
||||||
|
assert.equal(result.hasYamlLoadable, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// detectNewComponents
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
describe('detectNewComponents', () => {
|
||||||
|
it('new top-level __init__.py sets new-component', async () => {
|
||||||
|
const prFiles = [
|
||||||
|
{ filename: 'esphome/components/actuator/__init__.py', status: 'added', },
|
||||||
|
];
|
||||||
|
const result = await detectNewComponents(makeGithub(WITHOUT_SCHEMA), CONTEXT, prFiles);
|
||||||
|
assert.ok(result.labels.has('new-component'));
|
||||||
|
assert.equal(result.hasYamlLoadable, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('new top-level __init__.py with CONFIG_SCHEMA sets hasYamlLoadable', async () => {
|
||||||
|
const prFiles = [
|
||||||
|
{ filename: 'esphome/components/my_component/__init__.py', status: 'added' },
|
||||||
|
];
|
||||||
|
const result = await detectNewComponents(makeGithub(WITH_SCHEMA), CONTEXT, prFiles);
|
||||||
|
assert.ok(result.labels.has('new-component'));
|
||||||
|
assert.equal(result.hasYamlLoadable, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('new top-level __init__.py with IS_TARGET_PLATFORM sets new-target-platform', async () => {
|
||||||
|
const prFiles = [
|
||||||
|
{ filename: 'esphome/components/my_platform/__init__.py', status: 'added' },
|
||||||
|
];
|
||||||
|
const result = await detectNewComponents(makeGithub('IS_TARGET_PLATFORM = True'), CONTEXT, prFiles);
|
||||||
|
assert.ok(result.labels.has('new-component'));
|
||||||
|
assert.ok(result.labels.has('new-target-platform'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('modified __init__.py does not set new-component', async () => {
|
||||||
|
const prFiles = [
|
||||||
|
{ filename: 'esphome/components/existing/__init__.py', status: 'modified' },
|
||||||
|
];
|
||||||
|
const result = await detectNewComponents(makeGithub(WITH_SCHEMA), CONTEXT, prFiles);
|
||||||
|
assert.equal(result.labels.size, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('nested __init__.py does not set new-component', async () => {
|
||||||
|
const prFiles = [
|
||||||
|
{ filename: 'esphome/components/endstop/cover/__init__.py', status: 'added' },
|
||||||
|
];
|
||||||
|
const result = await detectNewComponents(makeGithub(WITH_SCHEMA), CONTEXT, prFiles);
|
||||||
|
assert.equal(result.labels.size, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
27
.github/workflows/ci-github-scripts.yml
vendored
Normal file
27
.github/workflows/ci-github-scripts.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: CI - GitHub Scripts
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [dev, beta, release]
|
||||||
|
paths:
|
||||||
|
- ".github/scripts/**"
|
||||||
|
- ".github/workflows/ci-github-scripts.yml"
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- ".github/scripts/**"
|
||||||
|
- ".github/workflows/ci-github-scripts.yml"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-auto-label-pr:
|
||||||
|
name: Test auto-label-pr scripts
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out code from GitHub
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
working-directory: .github/scripts/auto-label-pr
|
||||||
|
run: npm test
|
||||||
Reference in New Issue
Block a user