mirror of
https://github.com/alibaba/open-code-review.git
synced 2026-08-23 23:54:55 +00:00
Some checks are pending
CI / cross-compile (arm64, darwin) (push) Waiting to run
CI / cross-compile (arm64, linux) (push) Waiting to run
CI / cross-compile (arm64, windows) (push) Waiting to run
CI / test (push) Waiting to run
CI / cross-compile (amd64, darwin) (push) Waiting to run
CI / cross-compile (amd64, windows) (push) Waiting to run
CodeQL Advanced / Analyze (go) (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Pages / build (push) Waiting to run
Deploy Pages / deploy (push) Blocked by required conditions
* chore: add SPDX license headers to all source files
Add Apache-2.0 SPDX license identifiers and copyright notices to all
tracked .go, .sh, .js, .mjs, .ts, and .tsx source files.
Introduce scripts/verify-license.sh and scripts/add-license.sh for
automated verification and bulk addition of license headers. Integrate
the check into CI (ci.yml) and the Makefile (license-check target as
a prerequisite of the existing check target).
This satisfies the OpenSSF Best Practices Badge requirements for
copyright_per_file and license_per_file.
* fix: restore execute permissions on scripts
* docs: add license header instructions to CONTRIBUTING guides
* docs: add license header instructions to pages contributing guides
* fix(pages): strip unclosed HTML comment markers to satisfy CodeQL
* fix: apply code review suggestions for license scripts
- Fix portability: detect macOS vs Linux stat for permission copy
- Fix has_header: check both SPDX and copyright (match verify logic)
- Fix is_ignored: match on path boundaries to avoid false positives
- Fix year extraction: use consistent pipeline across both scripts
- Fix Bash 3.2 compat: quote array length expansion for set -u
* fix(pages): use loop-until-clean for HTML comment stripping (CodeQL)
* fix(pages): use split/join instead of replace to avoid CodeQL false positive
CodeQL's js/incomplete-multi-character-sanitization rule flags any
.replace() that removes multi-character sequences like '<!--...-->',
regardless of context. The data here comes from readFileSync on the
project's own index.html (no untrusted input), making this a false
positive. Using split(regex).join('') achieves the same result without
triggering the taint-tracking rule.
274 lines
7.8 KiB
Go
274 lines
7.8 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright 2026 alibaba/open-code-review Contributors
|
|
|
|
package diff
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseDiffText_StripsIndexHeadersFromPromptDiff(t *testing.T) {
|
|
diffText := `diff --git a/first.go b/first.go
|
|
index 1234567..89abcde 100644
|
|
--- a/first.go
|
|
+++ b/first.go
|
|
@@ -1,1 +1,2 @@
|
|
first
|
|
+index added-content
|
|
diff --git a/second.go b/second.go
|
|
new file mode 100644
|
|
index 0000000..7654321
|
|
--- /dev/null
|
|
+++ b/second.go
|
|
@@ -0,0 +1 @@
|
|
+package second
|
|
`
|
|
|
|
diffs, err := ParseDiffText(context.Background(), diffText, t.TempDir(), "", nil)
|
|
if err != nil {
|
|
t.Fatalf("ParseDiffText: %v", err)
|
|
}
|
|
if len(diffs) != 2 {
|
|
t.Fatalf("expected 2 diffs, got %d", len(diffs))
|
|
}
|
|
|
|
for _, d := range diffs {
|
|
if strings.Contains(d.Diff, "\nindex ") {
|
|
t.Errorf("prompt diff contains index header:\n%s", d.Diff)
|
|
}
|
|
}
|
|
if !strings.Contains(diffs[0].Diff, "diff --git a/first.go b/first.go") {
|
|
t.Errorf("prompt diff lost git header:\n%s", diffs[0].Diff)
|
|
}
|
|
if !strings.Contains(diffs[0].Diff, "+index added-content") {
|
|
t.Errorf("prompt diff lost index-prefixed hunk content:\n%s", diffs[0].Diff)
|
|
}
|
|
if !diffs[1].IsNew {
|
|
t.Error("new-file metadata was not preserved")
|
|
}
|
|
}
|
|
|
|
// TestParseDiffText_Rename guards against issue #99: a renamed file must be
|
|
// recognized via the "rename from"/"rename to" extended header lines so that
|
|
// the parser reads content at the NEW path instead of warning about the old
|
|
// path ("cannot read file ... exit status 128").
|
|
func TestParseDiffText_Rename(t *testing.T) {
|
|
diffText := `diff --git a/pkg/old name.go b/pkg/new name.go
|
|
similarity index 95%
|
|
rename from pkg/old name.go
|
|
rename to pkg/new name.go
|
|
index 1234567..89abcde 100644
|
|
--- a/pkg/old name.go
|
|
+++ b/pkg/new name.go
|
|
@@ -1,3 +1,3 @@
|
|
line1
|
|
-line2
|
|
+line2 changed
|
|
line3
|
|
`
|
|
diffs, err := ParseDiffText(context.Background(), diffText, t.TempDir(), "", nil)
|
|
if err != nil {
|
|
t.Fatalf("ParseDiffText: %v", err)
|
|
}
|
|
if len(diffs) != 1 {
|
|
t.Fatalf("expected 1 diff, got %d", len(diffs))
|
|
}
|
|
d := diffs[0]
|
|
if !d.IsRenamed {
|
|
t.Errorf("IsRenamed = false, want true")
|
|
}
|
|
if d.OldPath != "pkg/old name.go" {
|
|
t.Errorf("OldPath = %q, want %q", d.OldPath, "pkg/old name.go")
|
|
}
|
|
if d.NewPath != "pkg/new name.go" {
|
|
t.Errorf("NewPath = %q, want %q", d.NewPath, "pkg/new name.go")
|
|
}
|
|
if d.IsNew || d.IsDeleted {
|
|
t.Errorf("IsNew/IsDeleted = %v/%v, want false/false", d.IsNew, d.IsDeleted)
|
|
}
|
|
}
|
|
|
|
// TestParseDiffText_PureRename covers a 100% similarity rename, which carries
|
|
// no hunks and no ---/+++ lines at all.
|
|
func TestParseDiffText_PureRename(t *testing.T) {
|
|
diffText := `diff --git a/old.go b/new.go
|
|
similarity index 100%
|
|
rename from old.go
|
|
rename to new.go
|
|
`
|
|
diffs, err := ParseDiffText(context.Background(), diffText, t.TempDir(), "", nil)
|
|
if err != nil {
|
|
t.Fatalf("ParseDiffText: %v", err)
|
|
}
|
|
if len(diffs) != 1 {
|
|
t.Fatalf("expected 1 diff, got %d", len(diffs))
|
|
}
|
|
d := diffs[0]
|
|
if !d.IsRenamed || d.OldPath != "old.go" || d.NewPath != "new.go" {
|
|
t.Errorf("got IsRenamed=%v OldPath=%q NewPath=%q, want true/old.go/new.go",
|
|
d.IsRenamed, d.OldPath, d.NewPath)
|
|
}
|
|
}
|
|
|
|
// TestParseDiffText_DeletedFile guards the /dev/null detection: git emits
|
|
// "+++ /dev/null" WITHOUT the b/ prefix, which the old regexes required, so
|
|
// deletions were misclassified and triggered a doomed `git show ref:path`.
|
|
func TestParseDiffText_DeletedFile(t *testing.T) {
|
|
diffText := `diff --git a/gone.go b/gone.go
|
|
deleted file mode 100644
|
|
index 1234567..0000000
|
|
--- a/gone.go
|
|
+++ /dev/null
|
|
@@ -1,2 +0,0 @@
|
|
-line1
|
|
-line2
|
|
`
|
|
diffs, err := ParseDiffText(context.Background(), diffText, t.TempDir(), "", nil)
|
|
if err != nil {
|
|
t.Fatalf("ParseDiffText: %v", err)
|
|
}
|
|
if len(diffs) != 1 {
|
|
t.Fatalf("expected 1 diff, got %d", len(diffs))
|
|
}
|
|
d := diffs[0]
|
|
if !d.IsDeleted {
|
|
t.Errorf("IsDeleted = false, want true")
|
|
}
|
|
if d.NewPath != "/dev/null" {
|
|
t.Errorf("NewPath = %q, want /dev/null", d.NewPath)
|
|
}
|
|
if d.OldPath != "gone.go" {
|
|
t.Errorf("OldPath = %q, want gone.go", d.OldPath)
|
|
}
|
|
}
|
|
|
|
// TestParseDiffText_NewFile covers "--- /dev/null" (no a/ prefix).
|
|
func TestParseDiffText_NewFile(t *testing.T) {
|
|
diffText := `diff --git a/fresh.go b/fresh.go
|
|
new file mode 100644
|
|
index 0000000..1234567
|
|
--- /dev/null
|
|
+++ b/fresh.go
|
|
@@ -0,0 +1,2 @@
|
|
+line1
|
|
+line2
|
|
`
|
|
repo := t.TempDir()
|
|
diffs, err := ParseDiffText(context.Background(), diffText, repo, "", nil)
|
|
if err != nil {
|
|
t.Fatalf("ParseDiffText: %v", err)
|
|
}
|
|
if len(diffs) != 1 {
|
|
t.Fatalf("expected 1 diff, got %d", len(diffs))
|
|
}
|
|
d := diffs[0]
|
|
if !d.IsNew {
|
|
t.Errorf("IsNew = false, want true")
|
|
}
|
|
if d.IsDeleted {
|
|
t.Errorf("IsDeleted = true, want false")
|
|
}
|
|
if d.Insertions != 2 {
|
|
t.Errorf("Insertions = %d, want 2", d.Insertions)
|
|
}
|
|
}
|
|
|
|
// TestParseDiffText_BinaryMarkerAnchored guards two binary-detection cases:
|
|
// a text file whose CONTENT mentions "Binary files " must not be classified
|
|
// as binary (the unanchored regex used to match any line in the section and
|
|
// the file was silently excluded from review), while a real binary diff must
|
|
// still be detected.
|
|
func TestParseDiffText_BinaryMarkerAnchored(t *testing.T) {
|
|
diffText := `diff --git a/docs.md b/docs.md
|
|
index 1234567..89abcde 100644
|
|
--- a/docs.md
|
|
+++ b/docs.md
|
|
@@ -1,2 +1,3 @@
|
|
line1
|
|
+Note: Binary files are handled specially by git.
|
|
line2
|
|
diff --git a/blob.bin b/blob.bin
|
|
index 1234567..89abcde 100644
|
|
Binary files a/blob.bin and b/blob.bin differ
|
|
`
|
|
diffs, err := ParseDiffText(context.Background(), diffText, t.TempDir(), "", nil)
|
|
if err != nil {
|
|
t.Fatalf("ParseDiffText: %v", err)
|
|
}
|
|
if len(diffs) != 2 {
|
|
t.Fatalf("expected 2 diffs, got %d", len(diffs))
|
|
}
|
|
if diffs[0].IsBinary {
|
|
t.Errorf("docs.md IsBinary = true, want false (content line mentioning "+
|
|
"'Binary files ' must not mark the file binary); diff:\n%s", diffText)
|
|
}
|
|
if diffs[0].Insertions != 1 {
|
|
t.Errorf("docs.md Insertions = %d, want 1", diffs[0].Insertions)
|
|
}
|
|
if !diffs[1].IsBinary {
|
|
t.Errorf("blob.bin IsBinary = false, want true")
|
|
}
|
|
}
|
|
|
|
// TestParseDiffText_CountsContentLinesWithPlusMinusPrefix covers content
|
|
// lines that themselves begin with "++"/"--": an added line "++i" renders in
|
|
// the diff as "+++i", and the old "exclude +++/--- header" guard used to drop
|
|
// it from the insertion count (same for deletions), skewing per-file stats
|
|
// and the changeLines threshold that gates the plan phase.
|
|
func TestParseDiffText_CountsContentLinesWithPlusMinusPrefix(t *testing.T) {
|
|
diffText := `diff --git a/counter.go b/counter.go
|
|
index 1234567..89abcde 100644
|
|
--- a/counter.go
|
|
+++ b/counter.go
|
|
@@ -1,3 +1,3 @@
|
|
func inc() {
|
|
---oldFlag
|
|
+++newFlag
|
|
}
|
|
`
|
|
diffs, err := ParseDiffText(context.Background(), diffText, t.TempDir(), "", nil)
|
|
if err != nil {
|
|
t.Fatalf("ParseDiffText: %v", err)
|
|
}
|
|
if len(diffs) != 1 {
|
|
t.Fatalf("expected 1 diff, got %d", len(diffs))
|
|
}
|
|
d := diffs[0]
|
|
if d.Insertions != 1 {
|
|
t.Errorf("Insertions = %d, want 1 (added content line \"+++newFlag\")", d.Insertions)
|
|
}
|
|
if d.Deletions != 1 {
|
|
t.Errorf("Deletions = %d, want 1 (deleted content line \"---oldFlag\")", d.Deletions)
|
|
}
|
|
}
|
|
|
|
// TestParseDiffText_DevNullStringInsideHunk ensures an added content line
|
|
// whose rendered form is exactly "+++ /dev/null" (i.e. the file gained a line
|
|
// reading "++ /dev/null") is treated as hunk content, not as the deleted-file
|
|
// header marker.
|
|
func TestParseDiffText_DevNullStringInsideHunk(t *testing.T) {
|
|
diffText := `diff --git a/paths.txt b/paths.txt
|
|
index 1234567..89abcde 100644
|
|
--- a/paths.txt
|
|
+++ b/paths.txt
|
|
@@ -1,1 +1,2 @@
|
|
first
|
|
+++ /dev/null
|
|
`
|
|
diffs, err := ParseDiffText(context.Background(), diffText, t.TempDir(), "", nil)
|
|
if err != nil {
|
|
t.Fatalf("ParseDiffText: %v", err)
|
|
}
|
|
if len(diffs) != 1 {
|
|
t.Fatalf("expected 1 diff, got %d", len(diffs))
|
|
}
|
|
d := diffs[0]
|
|
if d.IsDeleted {
|
|
t.Errorf("IsDeleted = true, want false (\"+++ /dev/null\" inside a hunk is content)")
|
|
}
|
|
if d.Insertions != 1 {
|
|
t.Errorf("Insertions = %d, want 1", d.Insertions)
|
|
}
|
|
}
|