Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

237 lines
6.0 KiB
Go

package clones
import (
"strings"
"testing"
)
func TestTokenizeKeywordsAndNormalisation(t *testing.T) {
src := `func add(a int, b int) int { return a + b }`
got := Tokenize(src)
want := []string{
"func", "v", "(", "v", "v", ",", "v", "v", ")", "v",
"{", "return", "v", "+", "v", "}",
}
if len(got) != len(want) {
t.Fatalf("token count: got %d %v, want %d %v", len(got), got, len(want), want)
}
for i := range want {
if got[i] != want[i] {
t.Errorf("token %d: got %q, want %q", i, got[i], want[i])
}
}
}
func TestTokenizeLiteralsAndOperators(t *testing.T) {
got := Tokenize(`x := "hello" + 42 == 0x1F`)
want := []string{"v", ":=", "s", "+", "0", "==", "0"}
if strings.Join(got, " ") != strings.Join(want, " ") {
t.Fatalf("got %v, want %v", got, want)
}
}
func TestTokenizeStringWithEscapes(t *testing.T) {
// The escaped quote must not terminate the literal early.
got := Tokenize(`return "a\"b" + c`)
want := []string{"return", "s", "+", "v"}
if strings.Join(got, " ") != strings.Join(want, " ") {
t.Fatalf("got %v, want %v", got, want)
}
}
const bodyOriginal = `
total := 0
for i := 0; i < len(items); i++ {
if items[i].Active {
total += items[i].Weight * factor
} else {
total -= items[i].Penalty
}
}
if total < 0 {
total = 0
}
return total
`
// Same logic, every identifier renamed — a textbook Type-2 clone.
const bodyRenamed = `
sum := 0
for idx := 0; idx < len(records); idx++ {
if records[idx].Enabled {
sum += records[idx].Score * multiplier
} else {
sum -= records[idx].Fine
}
}
if sum < 0 {
sum = 0
}
return sum
`
// Unrelated function of comparable size.
const bodyDifferent = `
conn, err := pool.Acquire(ctx)
if err != nil {
return nil, fmt.Errorf("acquire: %w", err)
}
defer conn.Release()
rows, err := conn.Query(ctx, statement, args...)
if err != nil {
return nil, err
}
defer rows.Close()
return scanAll(rows)
`
func TestComputeSignatureTooSmall(t *testing.T) {
if _, ok := ComputeSignature(`return a + b`); ok {
t.Fatal("tiny body should not produce a signature")
}
}
func TestComputeSignatureIdentical(t *testing.T) {
a, ok := ComputeSignature(bodyOriginal)
if !ok {
t.Fatal("expected a signature for a substantial body")
}
b, _ := ComputeSignature(bodyOriginal)
if a != b {
t.Fatal("identical bodies must produce identical signatures")
}
if EstimateJaccard(a, b) != 1.0 {
t.Fatalf("identical bodies must have Jaccard 1.0, got %v", EstimateJaccard(a, b))
}
}
func TestComputeSignatureRenamedIsClone(t *testing.T) {
a, ok1 := ComputeSignature(bodyOriginal)
b, ok2 := ComputeSignature(bodyRenamed)
if !ok1 || !ok2 {
t.Fatal("both bodies should produce signatures")
}
sim := EstimateJaccard(a, b)
if sim < DefaultThreshold {
t.Fatalf("renamed-variable clone should score >= %v, got %v", DefaultThreshold, sim)
}
}
func TestComputeSignatureUnrelatedIsNotClone(t *testing.T) {
a, _ := ComputeSignature(bodyOriginal)
c, ok := ComputeSignature(bodyDifferent)
if !ok {
t.Fatal("expected a signature for the unrelated body")
}
if sim := EstimateJaccard(a, c); sim >= DefaultThreshold {
t.Fatalf("unrelated functions should not be clones, got similarity %v", sim)
}
}
func TestEncodeDecodeRoundTrip(t *testing.T) {
sig, ok := ComputeSignature(bodyOriginal)
if !ok {
t.Fatal("expected a signature")
}
enc := EncodeSignature(sig)
if enc == "" {
t.Fatal("encoded signature is empty")
}
back, ok := DecodeSignature(enc)
if !ok {
t.Fatal("decode failed")
}
if back != sig {
t.Fatal("round-trip changed the signature")
}
}
func TestDecodeSignatureRejectsGarbage(t *testing.T) {
if _, ok := DecodeSignature(""); ok {
t.Error("empty string should not decode")
}
if _, ok := DecodeSignature("not-base64!!!"); ok {
t.Error("invalid base64 should not decode")
}
if _, ok := DecodeSignature("YWJj"); ok {
t.Error("wrong-length payload should not decode")
}
}
func mustSig(t *testing.T, body string) Signature {
t.Helper()
s, ok := ComputeSignature(body)
if !ok {
t.Fatalf("body did not produce a signature")
}
return s
}
func TestDetectPairs(t *testing.T) {
items := []Item{
{ID: "a", Sig: mustSig(t, bodyOriginal)},
{ID: "b", Sig: mustSig(t, bodyRenamed)},
{ID: "c", Sig: mustSig(t, bodyDifferent)},
}
pairs := DetectPairs(items, 0) // 0 → DefaultThreshold
if len(pairs) != 1 {
t.Fatalf("expected exactly one clone pair, got %d: %+v", len(pairs), pairs)
}
p := pairs[0]
if p.A != "a" || p.B != "b" {
t.Fatalf("expected pair (a,b), got (%s,%s)", p.A, p.B)
}
if p.Similarity < DefaultThreshold {
t.Fatalf("pair similarity %v below threshold", p.Similarity)
}
}
func TestDetectPairsCanonicalOrder(t *testing.T) {
// IDs supplied out of order must still come back canonicalised.
items := []Item{
{ID: "zeta", Sig: mustSig(t, bodyOriginal)},
{ID: "alpha", Sig: mustSig(t, bodyOriginal)},
}
pairs := DetectPairs(items, 0)
if len(pairs) != 1 {
t.Fatalf("expected one pair, got %d", len(pairs))
}
if pairs[0].A != "alpha" || pairs[0].B != "zeta" {
t.Fatalf("pair not canonicalised: %+v", pairs[0])
}
}
func TestClusterPairsTransitive(t *testing.T) {
pairs := []Pair{
{A: "a", B: "b", Similarity: 0.9},
{A: "b", B: "c", Similarity: 0.95},
{A: "x", B: "y", Similarity: 0.88},
}
clusters := ClusterPairs(pairs)
if len(clusters) != 2 {
t.Fatalf("expected 2 clusters, got %d: %+v", len(clusters), clusters)
}
// Largest cluster first.
if clusters[0].Size != 3 {
t.Fatalf("expected first cluster size 3, got %d", clusters[0].Size)
}
want := []string{"a", "b", "c"}
for i, m := range want {
if clusters[0].Members[i] != m {
t.Fatalf("cluster members %v, want %v", clusters[0].Members, want)
}
}
if clusters[0].AvgSimilarity <= 0 {
t.Fatal("cluster average similarity should be positive")
}
if clusters[1].Size != 2 {
t.Fatalf("expected second cluster size 2, got %d", clusters[1].Size)
}
}
func TestDetectPairsEmptyInput(t *testing.T) {
if pairs := DetectPairs(nil, 0); len(pairs) != 0 {
t.Fatalf("no items should yield no pairs, got %+v", pairs)
}
}