mirror of
https://github.com/astral-sh/ruff-action.git
synced 2026-05-18 23:20:14 +02:00
refactor version resolving (#353)
This commit is contained in:
committed by
GitHub
parent
9b8caf6c41
commit
0ce1b0bf8b
@@ -47,15 +47,17 @@ const mockCopyFile = jest.fn();
|
||||
const mockReaddir = jest.fn();
|
||||
|
||||
jest.unstable_mockModule("node:fs", () => ({
|
||||
default: {},
|
||||
promises: {
|
||||
copyFile: mockCopyFile,
|
||||
readdir: mockReaddir,
|
||||
},
|
||||
}));
|
||||
|
||||
const { downloadVersion, resolveVersion, rewriteToMirror } = await import(
|
||||
const { downloadVersion, rewriteToMirror } = await import(
|
||||
"../../src/download/download-version"
|
||||
);
|
||||
const { resolveVersion } = await import("../../src/version/resolve");
|
||||
|
||||
describe("download-version", () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
||||
|
||||
const info = jest.fn();
|
||||
const warning = jest.fn();
|
||||
|
||||
jest.unstable_mockModule("@actions/core", () => ({
|
||||
info,
|
||||
warning,
|
||||
}));
|
||||
|
||||
const { findRuffVersionInSpec } = await import("../../src/utils/pyproject");
|
||||
|
||||
describe("findRuffVersionInSpec", () => {
|
||||
beforeEach(() => {
|
||||
info.mockReset();
|
||||
warning.mockReset();
|
||||
});
|
||||
|
||||
describe("ruff dependency strings", () => {
|
||||
it("should extract version from 'ruff==0.9.3'", () => {
|
||||
const result = findRuffVersionInSpec("ruff==0.9.3");
|
||||
expect(result).toBe("0.9.3");
|
||||
expect(info).toHaveBeenCalledWith(
|
||||
"Found ruff version in requirements file: 0.9.3",
|
||||
);
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should extract version from 'ruff>=0.14'", () => {
|
||||
const result = findRuffVersionInSpec("ruff>=0.14");
|
||||
expect(result).toBe(">=0.14");
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should extract version from 'ruff ~=1.0.0'", () => {
|
||||
const result = findRuffVersionInSpec("ruff ~=1.0.0");
|
||||
expect(result).toBe("~=1.0.0");
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should extract version from 'ruff>=0.14,<1.0'", () => {
|
||||
const result = findRuffVersionInSpec("ruff>=0.14,<1.0");
|
||||
expect(result).toBe(">=0.14,<1.0");
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should extract version from 'ruff>=0.14,<2.0,!=1.5.0'", () => {
|
||||
const result = findRuffVersionInSpec("ruff>=0.14,<2.0,!=1.5.0");
|
||||
expect(result).toBe(">=0.14,<2.0,!=1.5.0");
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return undefined for non-ruff dependency 'another-dep 0.1.6'", () => {
|
||||
const result = findRuffVersionInSpec("another-dep 0.1.6");
|
||||
expect(result).toBeUndefined();
|
||||
expect(info).not.toHaveBeenCalled();
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return undefined for non-ruff dependency 'another-dep==0.1.6'", () => {
|
||||
const result = findRuffVersionInSpec("another-dep==0.1.6");
|
||||
expect(result).toBeUndefined();
|
||||
expect(info).not.toHaveBeenCalled();
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should strip trailing backslash", () => {
|
||||
const result = findRuffVersionInSpec("ruff==0.9.3 \\");
|
||||
expect(result).toBe("0.9.3");
|
||||
expect(info).toHaveBeenCalledWith(
|
||||
"Found ruff version in requirements file: 0.9.3",
|
||||
);
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should strip trailing backslash with whitespace", () => {
|
||||
const result = findRuffVersionInSpec(" ruff==0.9.3 \\ ");
|
||||
expect(result).toBe("0.9.3");
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("environment markers", () => {
|
||||
it("should strip python_version environment marker", () => {
|
||||
const result = findRuffVersionInSpec(
|
||||
'ruff>=0.14 ; python_version >= "3.11"',
|
||||
);
|
||||
expect(result).toBe(">=0.14");
|
||||
expect(info).toHaveBeenCalledWith(
|
||||
"Found ruff version in requirements file: >=0.14",
|
||||
);
|
||||
expect(warning).toHaveBeenCalledWith(
|
||||
"Environment markers are ignored. ruff is a standalone tool that works independently of Python version.",
|
||||
);
|
||||
});
|
||||
|
||||
it("should strip sys_platform environment marker", () => {
|
||||
const result = findRuffVersionInSpec(
|
||||
"ruff==0.9.3 ; sys_platform == 'linux'",
|
||||
);
|
||||
expect(result).toBe("0.9.3");
|
||||
expect(warning).toHaveBeenCalledWith(
|
||||
"Environment markers are ignored. ruff is a standalone tool that works independently of Python version.",
|
||||
);
|
||||
});
|
||||
|
||||
it("should strip multiple environment markers", () => {
|
||||
const result = findRuffVersionInSpec(
|
||||
'ruff>=0.14 ; python_version >= "3.11" and sys_platform == "linux"',
|
||||
);
|
||||
expect(result).toBe(">=0.14");
|
||||
expect(warning).toHaveBeenCalledWith(
|
||||
"Environment markers are ignored. ruff is a standalone tool that works independently of Python version.",
|
||||
);
|
||||
});
|
||||
|
||||
it("should handle environment markers with multiple constraints", () => {
|
||||
const result = findRuffVersionInSpec(
|
||||
'ruff>=0.14,<1.0 ; python_version >= "3.11"',
|
||||
);
|
||||
expect(result).toBe(">=0.14,<1.0");
|
||||
expect(warning).toHaveBeenCalledWith(
|
||||
"Environment markers are ignored. ruff is a standalone tool that works independently of Python version.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("edge cases", () => {
|
||||
it("should handle whitespace", () => {
|
||||
const result = findRuffVersionInSpec(" ruff >=0.14 ");
|
||||
expect(result).toBe(">=0.14");
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle whitespace with environment markers", () => {
|
||||
const result = findRuffVersionInSpec(
|
||||
" ruff >=0.14 ; python_version >= '3.11' ",
|
||||
);
|
||||
expect(result).toBe(">=0.14");
|
||||
expect(warning).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return undefined for empty string", () => {
|
||||
const result = findRuffVersionInSpec("");
|
||||
expect(result).toBeUndefined();
|
||||
expect(info).not.toHaveBeenCalled();
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return undefined for whitespace only", () => {
|
||||
const result = findRuffVersionInSpec(" ");
|
||||
expect(result).toBeUndefined();
|
||||
expect(info).not.toHaveBeenCalled();
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return undefined for just semicolon", () => {
|
||||
const result = findRuffVersionInSpec(";");
|
||||
expect(result).toBeUndefined();
|
||||
expect(info).not.toHaveBeenCalled();
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle exact example from issue #256", () => {
|
||||
const result = findRuffVersionInSpec(
|
||||
'ruff>=0.14 ; python_version >= "3.11"',
|
||||
);
|
||||
expect(result).toBe(">=0.14");
|
||||
expect(info).toHaveBeenCalledWith(
|
||||
"Found ruff version in requirements file: >=0.14",
|
||||
);
|
||||
expect(warning).toHaveBeenCalledWith(
|
||||
"Environment markers are ignored. ruff is a standalone tool that works independently of Python version.",
|
||||
);
|
||||
});
|
||||
|
||||
it("should handle single-quoted environment markers", () => {
|
||||
const result = findRuffVersionInSpec(
|
||||
"ruff>=0.14 ; python_version >= '3.11'",
|
||||
);
|
||||
expect(result).toBe(">=0.14");
|
||||
expect(warning).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle double-quoted environment markers", () => {
|
||||
const result = findRuffVersionInSpec(
|
||||
'ruff>=0.14 ; python_version >= "3.11"',
|
||||
);
|
||||
expect(result).toBe(">=0.14");
|
||||
expect(warning).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,128 @@
|
||||
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
||||
|
||||
const info = jest.fn();
|
||||
const warning = jest.fn();
|
||||
|
||||
jest.unstable_mockModule("@actions/core", () => ({
|
||||
debug: jest.fn(),
|
||||
info,
|
||||
warning,
|
||||
}));
|
||||
|
||||
const { findRuffVersionInSpec, getRuffVersionFromFile } = await import(
|
||||
"../../src/version/file-parser"
|
||||
);
|
||||
|
||||
describe("file-parser", () => {
|
||||
beforeEach(() => {
|
||||
info.mockReset();
|
||||
warning.mockReset();
|
||||
});
|
||||
|
||||
describe("findRuffVersionInSpec", () => {
|
||||
it("extracts version from 'ruff==0.9.3'", () => {
|
||||
const result = findRuffVersionInSpec("ruff==0.9.3");
|
||||
expect(result).toBe("0.9.3");
|
||||
expect(info).toHaveBeenCalledWith(
|
||||
"Found ruff version in requirements file: 0.9.3",
|
||||
);
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("extracts version from 'ruff>=0.14'", () => {
|
||||
const result = findRuffVersionInSpec("ruff>=0.14");
|
||||
expect(result).toBe(">=0.14");
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("extracts version from 'ruff ~=1.0.0'", () => {
|
||||
const result = findRuffVersionInSpec("ruff ~=1.0.0");
|
||||
expect(result).toBe("~=1.0.0");
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("extracts version from 'ruff>=0.14,<1.0'", () => {
|
||||
const result = findRuffVersionInSpec("ruff>=0.14,<1.0");
|
||||
expect(result).toBe(">=0.14,<1.0");
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("extracts version from 'ruff>=0.14,<2.0,!=1.5.0'", () => {
|
||||
const result = findRuffVersionInSpec("ruff>=0.14,<2.0,!=1.5.0");
|
||||
expect(result).toBe(">=0.14,<2.0,!=1.5.0");
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns undefined for non-ruff dependencies", () => {
|
||||
const result = findRuffVersionInSpec("another-dep==0.1.6");
|
||||
expect(result).toBeUndefined();
|
||||
expect(info).not.toHaveBeenCalled();
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("strips trailing backslash", () => {
|
||||
const result = findRuffVersionInSpec("ruff==0.9.3 \\");
|
||||
expect(result).toBe("0.9.3");
|
||||
expect(info).toHaveBeenCalledWith(
|
||||
"Found ruff version in requirements file: 0.9.3",
|
||||
);
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("strips environment markers and warns", () => {
|
||||
const result = findRuffVersionInSpec(
|
||||
'ruff>=0.14 ; python_version >= "3.11"',
|
||||
);
|
||||
expect(result).toBe(">=0.14");
|
||||
expect(info).toHaveBeenCalledWith(
|
||||
"Found ruff version in requirements file: >=0.14",
|
||||
);
|
||||
expect(warning).toHaveBeenCalledWith(
|
||||
"Environment markers are ignored. ruff is a standalone tool that works independently of Python version.",
|
||||
);
|
||||
});
|
||||
|
||||
it("handles whitespace", () => {
|
||||
const result = findRuffVersionInSpec(" ruff >=0.14 ");
|
||||
expect(result).toBe(">=0.14");
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns undefined for empty strings", () => {
|
||||
const result = findRuffVersionInSpec("");
|
||||
expect(result).toBeUndefined();
|
||||
expect(info).not.toHaveBeenCalled();
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getRuffVersionFromFile", () => {
|
||||
it("reads the version from requirements.txt", () => {
|
||||
const result = getRuffVersionFromFile(
|
||||
"__tests__/fixtures/requirements.txt",
|
||||
);
|
||||
expect(result).toBe("0.9.0");
|
||||
});
|
||||
|
||||
it("reads the version from requirements files with hashes", () => {
|
||||
const result = getRuffVersionFromFile(
|
||||
"__tests__/fixtures/requirements-with-hash.txt",
|
||||
);
|
||||
expect(result).toBe("0.9.0");
|
||||
});
|
||||
|
||||
it("reads the version from pyproject.toml dependencies", () => {
|
||||
const result = getRuffVersionFromFile(
|
||||
"__tests__/fixtures/pyproject.toml",
|
||||
);
|
||||
expect(result).toBe("0.9.3");
|
||||
});
|
||||
|
||||
it("reads the version from Poetry dependencies", () => {
|
||||
const result = getRuffVersionFromFile(
|
||||
"__tests__/fixtures/pyproject-dependency-poetry-project/pyproject.toml",
|
||||
);
|
||||
expect(result).toBe("~0.8.2");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,164 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import {
|
||||
afterEach,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
jest,
|
||||
} from "@jest/globals";
|
||||
|
||||
const debug = jest.fn();
|
||||
const info = jest.fn();
|
||||
const warning = jest.fn();
|
||||
|
||||
jest.unstable_mockModule("@actions/core", () => ({
|
||||
debug,
|
||||
info,
|
||||
warning,
|
||||
}));
|
||||
|
||||
const { resolveVersionRequest } = await import(
|
||||
"../../src/version/version-request-resolver"
|
||||
);
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
function createTempProject(files: Record<string, string> = {}): string {
|
||||
const dir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), "ruff-action-version-test-"),
|
||||
);
|
||||
tempDirs.push(dir);
|
||||
|
||||
for (const [relativePath, content] of Object.entries(files)) {
|
||||
const filePath = path.join(dir, relativePath);
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
fs.writeFileSync(filePath, content);
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const dir of tempDirs.splice(0)) {
|
||||
fs.rmSync(dir, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
describe("resolveVersionRequest", () => {
|
||||
beforeEach(() => {
|
||||
debug.mockReset();
|
||||
info.mockReset();
|
||||
warning.mockReset();
|
||||
});
|
||||
|
||||
it("prefers explicit input over workspace discovery", () => {
|
||||
const workspaceRoot = createTempProject({
|
||||
"pyproject.toml": `[project]\ndependencies = ["ruff==0.5.14"]\n`,
|
||||
});
|
||||
|
||||
const request = resolveVersionRequest({
|
||||
sourceDirectory: workspaceRoot,
|
||||
version: "==0.6.0",
|
||||
workspaceRoot,
|
||||
});
|
||||
|
||||
expect(request).toEqual({
|
||||
source: "input",
|
||||
specifier: "0.6.0",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses requirements.txt when it is passed via version-file", () => {
|
||||
const workspaceRoot = createTempProject({
|
||||
"requirements.txt": "ruff==0.6.17\nruff-api==0.1.0\n",
|
||||
});
|
||||
|
||||
const request = resolveVersionRequest({
|
||||
sourceDirectory: workspaceRoot,
|
||||
versionFile: path.join(workspaceRoot, "requirements.txt"),
|
||||
workspaceRoot,
|
||||
});
|
||||
|
||||
expect(request).toEqual({
|
||||
format: "requirements",
|
||||
source: "version-file",
|
||||
sourcePath: path.join(workspaceRoot, "requirements.txt"),
|
||||
specifier: "0.6.17",
|
||||
});
|
||||
});
|
||||
|
||||
it("warns and falls back to latest when version-file does not resolve a version", () => {
|
||||
const workspaceRoot = createTempProject({
|
||||
"requirements.txt": "ruff-api==0.1.0\n",
|
||||
});
|
||||
|
||||
const request = resolveVersionRequest({
|
||||
sourceDirectory: workspaceRoot,
|
||||
versionFile: path.join(workspaceRoot, "requirements.txt"),
|
||||
workspaceRoot,
|
||||
});
|
||||
|
||||
expect(request).toEqual({
|
||||
source: "default",
|
||||
specifier: "latest",
|
||||
});
|
||||
expect(warning).toHaveBeenCalledWith(
|
||||
`Could not parse version from ${path.join(workspaceRoot, "requirements.txt")}. Using latest version.`,
|
||||
);
|
||||
});
|
||||
|
||||
it("discovers pyproject.toml by searching upward from src", () => {
|
||||
const workspaceRoot = createTempProject({
|
||||
"pyproject.toml": `[project]\ndependencies = ["ruff==0.10.0"]\n`,
|
||||
"subproject/nested/example.py": 'print("hello")\n',
|
||||
});
|
||||
const sourceDirectory = path.join(workspaceRoot, "subproject", "nested");
|
||||
|
||||
const request = resolveVersionRequest({
|
||||
sourceDirectory,
|
||||
workspaceRoot,
|
||||
});
|
||||
|
||||
expect(request).toEqual({
|
||||
format: "pyproject.toml",
|
||||
source: "pyproject.toml",
|
||||
sourcePath: path.join(workspaceRoot, "pyproject.toml"),
|
||||
specifier: "0.10.0",
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to latest when no workspace version source is found", () => {
|
||||
const workspaceRoot = createTempProject({
|
||||
"subproject/example.py": 'print("hello")\n',
|
||||
});
|
||||
|
||||
const request = resolveVersionRequest({
|
||||
sourceDirectory: path.join(workspaceRoot, "subproject"),
|
||||
workspaceRoot,
|
||||
});
|
||||
|
||||
expect(request).toEqual({
|
||||
source: "default",
|
||||
specifier: "latest",
|
||||
});
|
||||
expect(info).toHaveBeenCalledWith(
|
||||
"Could not find pyproject.toml. Using latest version.",
|
||||
);
|
||||
});
|
||||
|
||||
it("throws when both version and version-file are specified", () => {
|
||||
const workspaceRoot = createTempProject();
|
||||
|
||||
expect(() =>
|
||||
resolveVersionRequest({
|
||||
sourceDirectory: workspaceRoot,
|
||||
version: "0.6.0",
|
||||
versionFile: path.join(workspaceRoot, "requirements.txt"),
|
||||
workspaceRoot,
|
||||
}),
|
||||
).toThrow("It is not allowed to specify both version and version-file");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user