String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
err := os.WriteFile(flagsFileName, []byte(tc), 0644)
1package main23import (4 "fmt"5 "os"6 "os/exec"7 "path/filepath"8 "regexp"9 "runtime"10 "slices"11 "strconv"12 "strings"13 "testing"14)1516const sccTestFlag string = "-test.main"1718var sccBinPath = os.Args[0]1920func TestMain(m *testing.M) {21 idx := slices.Index(os.Args, sccTestFlag)22 if idx != -1 {23 os.Args = slices.Delete(os.Args, idx, idx+1)24 main()25 return26 }2728 os.Exit(m.Run())29}3031func runSCC(args ...string) (string, error) {32 args = slices.Insert(args, 0, sccTestFlag)33 cmd := exec.Command(sccBinPath, args...)34 // Force SCC_CONFIG_PATH empty (treated as unset) so a global config in the35 // developer's environment cannot pollute these runs - an unreadable one would36 // otherwise make scc exit non-zero and fail every test here.37 cmd.Env = append(os.Environ(), SccConfigEnv+"=")38 res, err := cmd.CombinedOutput()39 return string(res), err40}4142func TestNoGitIgnore(t *testing.T) {43 tmpDir := t.TempDir()44 ignoreFileName := filepath.Join(tmpDir, ".gitignore")45 err := os.WriteFile(ignoreFileName, []byte("ignored.xml\n"), 0644)46 if err != nil {47 t.Fatal(err)48 }49 xmlFileName := filepath.Join(tmpDir, "ignored.xml")50 err = os.WriteFile(xmlFileName, []byte(`<?xml version="1.0" encoding="UTF-8"?>`), 0644)51 if err != nil {52 t.Fatal(err)53 }5455 output, err := runSCC(tmpDir)56 if err != nil {57 t.Fatal(err)58 }59 if strings.Contains(output, "XML") {60 t.Fatalf("test --no-gitignore failed, output:\n%s", output)61 }6263 output, err = runSCC("--no-gitignore", tmpDir)64 if err != nil {65 t.Fatal(err)66 }67 if !strings.Contains(output, "XML") {68 t.Fatalf("test --no-gitignore failed, output:\n%s", output)69 }70}7172func TestIssue82(t *testing.T) {73 t.Parallel()74 // Regression issue https://github.com/boyter/scc/issues/8275 output1, err := runSCC(".")76 if err != nil {77 t.Fatal(err)78 }7980 pwd, err := os.Getwd()81 if err != nil {82 t.Fatal(err)83 }84 output2, err := runSCC(pwd)85 if err != nil {86 t.Fatal(err)87 }8889 if output1 != output2 {90 t.Fatalf("`./scc .` not equal to `./scc ${PWD}`")91 }92}9394func TestIncludeExt(t *testing.T) {95 t.Parallel()96 // Regression issue https://github.com/boyter/scc/issues/10897 output, err := runSCC("--include-ext", "go", "examples/language")98 if err != nil {99 t.Fatal(err)100 }101 if !strings.Contains(output, "Go") || strings.Contains(output, "Java") {102 t.Fatalf("include-ext check failed, output:\n%s", output)103 }104}105106func TestIssue115(t *testing.T) {107 t.Parallel()108 // Regression issue https://github.com/boyter/scc/issues/115109 output, err := runSCC("examples/issue115/.test/file")110 if err != nil {111 t.Fatal(err)112 }113 if strings.Contains(output, "Perl") {114 t.Fatalf("Should not print Perl, output:\n%s", output)115 }116}117118func TestIssue120(t *testing.T) {119 t.Parallel()120 // Regression issue https://github.com/boyter/scc/issues/120121 output, err := runSCC("-i", "java", "./examples/issue120")122 if err != nil {123 t.Fatal(err)124 }125 if strings.Contains(output, "Perl") {126 t.Fatal("extension param should ignore Shebang")127 }128}129130func TestIssue152(t *testing.T) {131 t.Parallel()132 // Regression issue https://github.com/boyter/scc/issues/152133 output, err := runSCC("-i", "css", "./examples/issue152/")134 if err != nil {135 t.Fatal(err)136 }137 if !strings.Contains(output, "CSS") {138 t.Fatalf("`-i css` extension check failed, output:\n%s", output)139 }140}141142func TestIssue250(t *testing.T) {143 // Regression issue https://github.com/boyter/scc/issues/250144 output1, err := runSCC("--exclude-dir", "examples/")145 if err != nil {146 t.Fatal(err)147 }148 output2, err := runSCC("--exclude-dir", "examples")149 if err != nil {150 t.Fatal(err)151 }152153 if output1 != output2 {154 t.Fatalf("examples exclude-dir check failed, output1:\n%s, output2:\n%s", output1, output2)155 }156}157158func TestIssue259(t *testing.T) {159 // Regression issue https://github.com/boyter/scc/issues/259160 output, err := runSCC("-f", "csv", "--exclude-ext", "go")161 if err != nil {162 t.Fatal(err)163 }164165 if strings.Contains(output, "Go,") {166 t.Fatalf("exclude-ext check failed, output:\n%s", output)167 }168}169170func TestIssue260(t *testing.T) {171 t.Parallel()172 // Regression issue https://github.com/boyter/scc/issues/260173 _, err := runSCC("-d", "examples/issue260/")174 if err != nil {175 t.Fatalf("duplicate empty crash: %v", err)176 }177}178179func TestIssue345(t *testing.T) {180 t.Parallel()181 // Regression issue https://github.com/boyter/scc/issues/345182 const expectedOutput = "C++,4,3,1,0,0,76,1,0"183 output, err := runSCC("-f", "csv", "--no-scc-ignore", "examples/issue345/")184 if err != nil {185 t.Fatal(err)186 }187 lines := strings.Split(output, "\n")188 if len(lines) < 2 {189 t.Fatalf("wrong output: %s", output)190 }191 if lines[1] != expectedOutput {192 t.Fatalf("got: %s, want: %s", lines[1], expectedOutput)193 }194}195196func TestIssue379(t *testing.T) {197 t.Parallel()198 // Regression issue https://github.com/boyter/scc/issues/379199 const expectedOutput = "Python,7,4,2,1,1,83,1,0"200 output, err := runSCC("-f", "csv", "--no-scc-ignore", "examples/issue379/")201 if err != nil {202 t.Fatal(err)203 }204 lines := strings.Split(output, "\n")205 if len(lines) < 2 {206 t.Fatalf("wrong output: %s", output)207 }208 if lines[1] != expectedOutput {209 t.Fatalf("got: %s, want: %s", lines[1], expectedOutput)210 }211}212213func TestIssue457(t *testing.T) {214 t.Parallel()215 // Regression issue https://github.com/boyter/scc/issues/457216 output, err := runSCC("-M", ".*")217 if err != nil {218 t.Fatal(err)219 }220 if !strings.Contains(output, "0.000 megabytes") {221 t.Fatalf("Issue 457 test failed, output:\n%s", output)222 }223}224225func TestIssue564(t *testing.T) {226 t.Parallel()227 // Regression issue https://github.com/boyter/scc/issues/564228 const expectedPythonOutput = "Python,3,3,0,0,0,84,3,0"229 const expectedGoOutput = "Go,6,4,0,2,0,58,2,0"230 output, err := runSCC("-f", "csv", "--no-scc-ignore", "examples/issue564/")231 if err != nil {232 t.Fatal(err)233 }234 lines := strings.Split(output, "\n")235 if len(lines) < 3 {236 t.Fatalf("wrong output: %s", output)237 }238 if lines[1] != expectedPythonOutput {239 t.Fatalf("got: %s, want: %s", lines[1], expectedPythonOutput)240 }241 if lines[2] != expectedGoOutput {242 t.Fatalf("got: %s, want: %s", lines[2], expectedGoOutput)243 }244}245246func TestIssue610(t *testing.T) {247 t.Parallel()248 // Regression issue https://github.com/boyter/scc/issues/610249 const expectedOutput = "TypeScript,11,7,2,2,1,214,1,0"250 output, err := runSCC("-f", "csv", "--no-scc-ignore", "examples/issue610/")251 if err != nil {252 t.Fatal(err)253 }254 lines := strings.Split(output, "\n")255 if len(lines) < 2 {256 t.Fatalf("wrong output: %s", output)257 }258 if lines[1] != expectedOutput {259 t.Fatalf("got: %s, want: %s", lines[1], expectedOutput)260 }261}262263func TestIssue339(t *testing.T) {264 t.Parallel()265 // Regression issue https://github.com/boyter/scc/issues/339266 output, err := runSCC("-f", "csv", "--no-scc-ignore", "examples/issue339/")267 if err != nil {268 t.Fatal(err)269 }270 if !strings.Contains(output, "MATLAB") {271 t.Errorf("can not find MATLAB, output: %s", output)272 }273 if !strings.Contains(output, "Objective C") {274 t.Errorf("can not find Objective C, output:\n%s", output)275 }276}277278func TestInvalidOption(t *testing.T) {279 t.Parallel()280 output, err := runSCC("--not-a-real-option")281 if err == nil {282 t.Fatal("scc should exit with error code")283 }284 if !strings.Contains(output, "Error: unknown flag: --not-a-real-option") {285 t.Fatalf("scc should report invalid options, output:\n%s", output)286 }287}288289func TestFileFlagSyntax(t *testing.T) {290 tmpDir := t.TempDir()291 flagsFileName := filepath.Join(tmpDir, "flags.txt")292 // include \n, \r\n and no line terminators293 testCases := []string{294 "go.mod\ngo.sum\nLICENSE\n",295 "go.mod\r\ngo.sum\r\nLICENSE\r\n",296 "go.mod\ngo.sum\nLICENSE",297 "go.mod\r\ngo.sum\r\nLICENSE",298 "go.mod\ngo.sum\r\nLICENSE",299 }300301 for _, tc := range testCases {302 err := os.WriteFile(flagsFileName, []byte(tc), 0644)303 if err != nil {304 t.Fatal(err)305 }306 _, err = runSCC("@" + flagsFileName)307 if err != nil {308 t.Errorf("flag syntax faild: %q, %v", tc, err)309 }310 }311}312313func TestLineLength(t *testing.T) {314 t.Parallel()315 output, err := runSCC("-m")316 if err != nil {317 t.Fatal(err)318 }319 if strings.Count(output, "MaxLine / MeanLine") < 2 {320 t.Fatalf("line length test failed, output:\n%s", output)321 }322}323324func TestFormatHTML(t *testing.T) {325 t.Parallel()326 output, err := runSCC("--format", "html")327 if err != nil {328 t.Fatal(err)329 }330 if !strings.Contains(output, "<title>scc html output</title>") {331 t.Fatalf("html format test failed, output:\n%s", output)332 }333}334335func TestFormatHTMLTable(t *testing.T) {336 t.Parallel()337 output, err := runSCC("--format", "html-table")338 if err != nil {339 t.Fatal(err)340 }341 if !strings.Contains(output, `<table id="scc-table">`) {342 t.Fatalf("html-table format test failed, output:\n%s", output)343 }344}345346func TestFormatSQL(t *testing.T) {347 t.Parallel()348 output, err := runSCC("--format", "sql")349 if err != nil {350 t.Fatal(err)351 }352 if !strings.Contains(output, "create table metadata ( -- github.com/boyter/scc") {353 t.Fatalf("sql format test failed, output:\n%s", output)354 }355}356357func TestFormatSQLInsert(t *testing.T) {358 t.Parallel()359 output, err := runSCC("--format", "sql-insert")360 if err != nil {361 t.Fatal(err)362 }363 if !strings.Contains(output, "begin transaction;\ninsert into t values(") {364 t.Fatalf("sql-insert format test failed, output:\n%s", output)365 }366}367368func TestMultipleFormatStdout(t *testing.T) {369 output, err := runSCC("--format-multi", "tabular:stdout,html:stdout,csv:stdout,sql:stdout")370 if err != nil {371 t.Fatal(err)372 }373374 tabularPattern := regexp.MustCompile(`Processed .+? bytes, .+? megabytes \(SI\)`)375 if !tabularPattern.MatchString(output) {376 t.Errorf("multi-format tabular failed, output:\n%s", output)377 }378379 if !strings.Contains(output, `<html lang="en"><head><meta charset="utf-8" /><title>scc html output</title>`) {380 t.Errorf("multi-format html failed, output:\n%s", output)381 }382383 if !strings.Contains(output, "Language,Lines,Code,Comments,Blanks,Complexity,Bytes,Files,ULOC") {384 t.Errorf("multi-format csv failed, output:\n%s", output)385 }386387 sqlPattern := regexp.MustCompile(`insert into t values\(.+?\);`)388 if !sqlPattern.MatchString(output) {389 t.Errorf("multi-format sql failed, output:\n%s", output)390 }391}392393func TestMultipleFormatWriteFile(t *testing.T) {394 tmpDir := t.TempDir()395 outputTabular := filepath.Join(tmpDir, "output.tab")396 outputWide := filepath.Join(tmpDir, "output.wide")397 outputJSON1 := filepath.Join(tmpDir, "output.json")398 outputJSON2 := filepath.Join(tmpDir, "output2.json")399 outputCSV := filepath.Join(tmpDir, "output.csv")400 outputYAML := filepath.Join(tmpDir, "output.yaml")401 outputHTML := filepath.Join(tmpDir, "output.html")402 outputHTMLTable := filepath.Join(tmpDir, "output_table.html")403 outputSQL := filepath.Join(tmpDir, "output.sql")404405 multiFormatArgs := fmt.Sprintf(406 "tabular:%s,wide:%s,json:%s,json2:%s,csv:%s,cloc-yaml:%s,html:%s,html-table:%s,sql:%s",407 outputTabular,408 outputWide,409 outputJSON1,410 outputJSON2,411 outputCSV,412 outputYAML,413 outputHTML,414 outputHTMLTable,415 outputSQL,416 )417418 _, err := runSCC("--format-multi", multiFormatArgs)419 if err != nil {420 t.Fatal(err)421 }422423 if info, err := os.Stat(outputTabular); err != nil || info.Size() <= 0 {424 t.Fatal("tabular write file test failed")425 }426 if info, err := os.Stat(outputWide); err != nil || info.Size() <= 0 {427 t.Fatal("wide write file test failed")428 }429 if info, err := os.Stat(outputJSON1); err != nil || info.Size() <= 0 {430 t.Fatal("json write file test failed")431 }432 if info, err := os.Stat(outputJSON2); err != nil || info.Size() <= 0 {433 t.Fatal("json2 write file test failed")434 }435 if info, err := os.Stat(outputCSV); err != nil || info.Size() <= 0 {436 t.Fatal("csv write file test failed")437 }438 if info, err := os.Stat(outputYAML); err != nil || info.Size() <= 0 {439 t.Fatal("cloc-yaml write file test failed")440 }441 if info, err := os.Stat(outputHTML); err != nil || info.Size() <= 0 {442 t.Fatal("html write file test failed")443 }444 if info, err := os.Stat(outputHTMLTable); err != nil || info.Size() <= 0 {445 t.Fatal("html-table write file test failed")446 }447 if info, err := os.Stat(outputSQL); err != nil || info.Size() <= 0 {448 t.Fatal("sql write file test failed")449 }450}451452// TestMultipleFormatWriteFileColonPath is a regression test for453// https://github.com/boyter/scc/issues/290 where --format-multi silently454// discarded any target containing a colon, such as a Windows absolute path455// like csv:C:\folder\out.csv, because the format:target pair was split on456// every colon rather than only the first one.457func TestMultipleFormatWriteFileColonPath(t *testing.T) {458 tmpDir := t.TempDir()459 outputCSV := filepath.Join(tmpDir, "C:output.csv")460461 _, err := runSCC("--format-multi", "csv:"+outputCSV)462 if err != nil {463 t.Fatal(err)464 }465466 if info, err := os.Stat(outputCSV); err != nil || info.Size() <= 0 {467 t.Fatalf("csv write to colon containing path failed for %s", outputCSV)468 }469}470471func TestRecursivelyIgnore(t *testing.T) {472 tmpDir := t.TempDir()473 err := os.WriteFile(filepath.Join(tmpDir, ".gitignore"), []byte("ignore-git.txt\n"), 0644)474 if err != nil {475 t.Fatal(err)476 }477 err = os.WriteFile(filepath.Join(tmpDir, ".ignore"), []byte("vendor/\nignore.txt\n"), 0644)478 if err != nil {479 t.Fatal(err)480 }481 err = os.Mkdir(filepath.Join(tmpDir, "ignore"), 0755)482 if err != nil {483 t.Fatal(err)484 }485 err = os.WriteFile(filepath.Join(tmpDir, "ignore", "README.md"), []byte("Files in here are to ensure that .ignore and .gitignore work recursively\n"), 0644)486 if err != nil {487 t.Fatal(err)488 }489 err = os.WriteFile(filepath.Join(tmpDir, "ignore", "ignore.txt"), []byte("testing\n"), 0644)490 if err != nil {491 t.Fatal(err)492 }493 err = os.WriteFile(filepath.Join(tmpDir, "ignore", "ignore-git.txt"), []byte("git\ntesting\n"), 0644)494 if err != nil {495 t.Fatal(err)496 }497498 output, err := runSCC("--by-file", "--no-scc-ignore", tmpDir)499 if err != nil {500 t.Fatal(err)501 }502 if strings.Contains(output, "ignore.txt") || strings.Contains(output, "ignore-git.txt") {503 t.Errorf("ignore recursive filter failed, output:\n%s", output)504 }505506 output, err = runSCC("--by-file", "--no-scc-ignore", "--no-ignore", tmpDir)507 if err != nil {508 t.Fatal(err)509 }510 if !strings.Contains(output, "ignore.txt") || strings.Contains(output, "ignore-git.txt") {511 t.Errorf("ignore recursive filter failed, output:\n%s", output)512 }513514 output, err = runSCC("--by-file", "--no-scc-ignore", "--no-gitignore", tmpDir)515 if err != nil {516 t.Fatal(err)517 }518 if strings.Contains(output, "ignore.txt") || !strings.Contains(output, "ignore-git.txt") {519 t.Errorf("ignore recursive filter failed, output:\n%s", output)520 }521522 output, err = runSCC("--by-file", "--no-scc-ignore", "--no-ignore", "--no-gitignore", tmpDir)523 if err != nil {524 t.Fatal(err)525 }526 if !strings.Contains(output, "ignore.txt") || !strings.Contains(output, "ignore-git.txt") {527 t.Errorf("ignore recursive filter failed, output:\n%s", output)528 }529}530531func TestMultipleGitIgnore(t *testing.T) {532 tmpDir := t.TempDir()533 err := os.WriteFile(filepath.Join(tmpDir, ".gitignore"), []byte("ignore.txt\n"), 0644)534 if err != nil {535 t.Fatal(err)536 }537 err = os.Mkdir(filepath.Join(tmpDir, "ignore"), 0755)538 if err != nil {539 t.Fatal(err)540 }541 err = os.WriteFile(filepath.Join(tmpDir, "ignore", ".gitignore"), []byte("ignore.java\n"), 0644)542 if err != nil {543 t.Fatal(err)544 }545 err = os.WriteFile(filepath.Join(tmpDir, "ignore", "ignore.java"), []byte("//test\n"), 0644)546 if err != nil {547 t.Fatal(err)548 }549550 output, err := runSCC(tmpDir)551 if err != nil {552 t.Fatal(err)553 }554 if strings.Contains(output, "Java") {555 t.Fatalf("multiple gitignore failed, output:\n%s", output)556 }557}558559func TestFlagSuggestion(t *testing.T) {560 t.Parallel()561 testCases := []struct {562 args []string563 expectedOutput string564 }{565 {566 args: []string{"--farmat"},567 expectedOutput: "The most similar flag of --farmat is:\n\t--format\n",568 },569 {570 args: []string{"--no-gignore"},571 expectedOutput: "The most similar flags of --no-gignore are:\n\t--no-ignore\n\t--no-gitignore\n",572 },573 }574575 for _, tc := range testCases {576 output, err := runSCC(tc.args...)577 if err == nil {578 t.Fatal("scc should exit with error code")579 }580 if !strings.Contains(output, tc.expectedOutput) {581 t.Errorf("wrong suggestion for %v, want: %s, got: %s", tc.args, tc.expectedOutput, output)582 }583 }584}585586func TestDeterministicOutput(t *testing.T) {587 output, err := runSCC(".")588 if err != nil {589 t.Fatal(err)590 }591 for range 10 {592 output2, err := runSCC(".")593 if err != nil {594 t.Fatal(err)595 }596 if output != output2 {597 t.Fatalf("want:\n%s, got:\n%s", output, output2)598 }599 }600}601602func TestDuplicates(t *testing.T) {603 for range 10 {604 output, err := runSCC("-f", "json", "-d", "./examples/duplicates/")605 if err != nil {606 t.Fatal(err)607 }608 if !strings.Contains(output, `"Count":1`) {609 t.Fatalf("duplicates check failed, output:\n%s", output)610 }611 }612}613614func TestCountAs(t *testing.T) {615 testCases := []struct {616 countAs string617 expected []string618 }{619 {620 countAs: "jsp:html",621 expected: []string{"HTML"},622 },623 {624 countAs: "JsP:html",625 expected: []string{"HTML"},626 },627 {628 countAs: "jsp:j2",629 expected: []string{"Jinja"},630 },631 {632 countAs: "jsp:html,new:java",633 expected: []string{"HTML", "Java"},634 },635 {636 countAs: "jsp:html,new:C Header",637 expected: []string{"HTML", "C Header"},638 },639 }640641 for _, tc := range testCases {642 output, err := runSCC("-f", "csv", "--count-as", tc.countAs, "./examples/countas/")643 if err != nil {644 t.Fatal(err)645 }646 for _, expectedLang := range tc.expected {647 if !strings.Contains(output, expectedLang+",") {648 t.Errorf("count as failed, count as: %s, output:\n%s", tc.countAs, output)649 }650 }651 }652}653654func TestCountAsPattern(t *testing.T) {655 // foo_spec.rb is relabelled to the new Ruby Spec category, app.rb stays Ruby656 output, err := runSCC("-f", "csv", "--count-as-pattern", "glob:*_spec.rb:Ruby Spec:Ruby", "./examples/countaspattern/")657 if err != nil {658 t.Fatal(err)659 }660661 // The minted category row, with non-zero comment and complexity counts which662 // proves the counting rules were cloned from the Ruby base language663 if !strings.Contains(output, "Ruby Spec,9,7,1,1,1,") {664 t.Errorf("count-as-pattern failed to produce Ruby Spec row, output:\n%s", output)665 }666 // The non matching file is still counted as plain Ruby667 if !strings.Contains(output, "Ruby,7,5,1,1,1,") {668 t.Errorf("count-as-pattern should leave app.rb as Ruby, output:\n%s", output)669 }670671 // The regex engine should produce the same relabelling672 output, err = runSCC("-f", "csv", "--count-as-pattern", `re:_spec\.rb$:Ruby Spec:Ruby`, "./examples/countaspattern/")673 if err != nil {674 t.Fatal(err)675 }676 if !strings.Contains(output, "Ruby Spec,") {677 t.Errorf("regex count-as-pattern failed, output:\n%s", output)678 }679}680681func TestCountAsPatternInvalidSkipped(t *testing.T) {682 // An unresolvable base language is reported and skipped, the run still works683 output, err := runSCC("-f", "csv", "--count-as-pattern", "glob:*_spec.rb:Ruby Spec:Nonexistent", "./examples/countaspattern/")684 if err != nil {685 t.Fatal(err)686 }687 if !strings.Contains(output, "is not a known language or extension") {688 t.Errorf("expected error message for unknown base language, output:\n%s", output)689 }690 if strings.Contains(output, "Ruby Spec,") {691 t.Errorf("Ruby Spec should not appear when the rule was skipped, output:\n%s", output)692 }693}694695func TestRemapUnknown(t *testing.T) {696 t.Parallel()697 output, err := runSCC("-f", "csv", "--remap-unknown", "-*- C++ -*-:C Header", "./examples/remap/unknown")698 if err != nil {699 t.Fatal(err)700 }701 if !strings.Contains(output, "C Header,") {702 t.Fatalf("remap unknown failed, output:\n%s", output)703 }704}705706func TestRemapAll(t *testing.T) {707 t.Parallel()708 output, err := runSCC("-f", "csv", "--remap-all", "-*- C++ -*-:C Header", "./examples/remap/java.java")709 if err != nil {710 t.Fatal(err)711 }712 if !strings.Contains(output, "C Header,") {713 t.Fatalf("remap all failed, output:\n%s", output)714 }715}716717func TestCocomoProjectType(t *testing.T) {718 projectTypes := []string{"organic", "semi-detached", "embedded", "custom,1,1,1,1"}719 for _, typ := range projectTypes {720 output, err := runSCC("--cocomo-project-type", typ)721 if err != nil {722 t.Fatal(err)723 }724 if !strings.Contains(output, fmt.Sprintf("Estimated Cost to Develop (%s)", typ)) ||725 !strings.Contains(output, fmt.Sprintf("Estimated Schedule Effort (%s)", typ)) ||726 !strings.Contains(output, fmt.Sprintf("Estimated People Required (%s)", typ)) {727 t.Errorf("check cocomo project type failed: %s", typ)728 }729 }730}731732func TestCocomoProjectTypeFallback(t *testing.T) {733 unknownTypes := []string{"doesnotexist", "custom,1,1,1"}734 for _, typ := range unknownTypes {735 output, err := runSCC("--cocomo-project-type", typ)736 if err != nil {737 t.Fatal(err)738 }739 if !strings.Contains(output, "Estimated Cost to Develop (organic)") ||740 !strings.Contains(output, "Estimated Schedule Effort (organic)") ||741 !strings.Contains(output, "Estimated People Required (organic)") {742 t.Errorf("check cocomo project type fallback failed: %s", typ)743 }744 }745}746747func TestOutputBytes(t *testing.T) {748 jsonOutput, err := runSCC("-f", "json")749 if err != nil {750 t.Fatal(err)751 }752 if !strings.Contains(jsonOutput, `"Bytes":`) {753 t.Errorf("json output does not contain `Bytes` field, output:\n%s", jsonOutput)754 }755756 output, err := runSCC()757 if err != nil {758 t.Fatal(err)759 }760 if !strings.Contains(output, "megabytes") {761 t.Errorf("output does not contain `megabytes`, output:\n%s", output)762 }763}764765func TestFileGCCount(t *testing.T) {766 const target = "./examples/duplicates"767 files, err := os.ReadDir(target)768 if err != nil {769 t.Fatal(err)770 }771772 output, err := runSCC("--file-gc-count", strconv.Itoa(len(files)-1), "-v", target)773 if err != nil {774 t.Fatal(err)775 }776 if !strings.Contains(output, "read file limit exceeded GC re-enabled") {777 t.Errorf("test file GC count failed, file count: %d, limit: %d", len(files), len(files)-1)778 }779780 output, err = runSCC("--file-gc-count", strconv.Itoa(len(files)+1), "-v", target)781 if err != nil {782 t.Fatal(err)783 }784 if strings.Contains(output, "read file limit exceeded GC re-enabled") {785 t.Errorf("test file GC count failed, file count: %d, limit: %d", len(files), len(files)+1)786 }787}788789func TestIncludeSymlinks(t *testing.T) {790 if runtime.GOOS == "windows" {791 t.Skip("skipping symlink test on Windows due to privilege requirements")792 }793794 tmpDir, err := filepath.EvalSymlinks(t.TempDir())795 if err != nil {796 t.Fatal(err)797 }798799 dirA := filepath.Join(tmpDir, "a")800 dirB := filepath.Join(tmpDir, "b")801 if err := os.Mkdir(dirA, 0755); err != nil {802 t.Fatal(err)803 }804 if err := os.Mkdir(dirB, 0755); err != nil {805 t.Fatal(err)806 }807808 const fileName = "source.go"809 if err := os.WriteFile(filepath.Join(dirA, fileName), []byte("package main\n"), 0644); err != nil {810 t.Fatal(err)811 }812 if err := os.WriteFile(filepath.Join(dirB, fileName), []byte("package main\n"), 0644); err != nil {813 t.Fatal(err)814 }815 // link to another dir, should be counted under --include-symlinks816 if err := os.Symlink(filepath.Join(dirB, fileName), filepath.Join(dirA, "link1.go")); err != nil {817 t.Fatal(err)818 }819 // link to the same dir, this should be ignored in all times820 if err := os.Symlink(filepath.Join(dirA, fileName), filepath.Join(dirA, "link2.go")); err != nil {821 t.Fatal(err)822 }823824 output, err := runSCC("-f", "json", "--no-scc-ignore", dirA)825 if err != nil {826 t.Fatal(err)827 }828 if !strings.Contains(output, `"Count":1`) {829 t.Errorf("count without symlink failed, output:\n%s", output)830 }831832 output, err = runSCC("-f", "json", "--no-scc-ignore", "--include-symlinks", dirA)833 if err != nil {834 t.Fatal(err)835 }836 if !strings.Contains(output, `"Count":2`) {837 t.Errorf("count includes symlink failed, output:\n%s", output)838 }839}840841func TestLanguageNameTruncate(t *testing.T) {842 output, err := runSCC("examples/language")843 if err != nil {844 t.Fatal(err)845 }846 if strings.Count(output, "Bitbucket Pipe…") != 1 {847 t.Errorf("`Bitbucket Pipeline` truncate test failed")848 }849 if strings.Count(output, "CloudFormation…") != 2 {850 t.Errorf("`CloudFormation (JSON)` and `CloudFormation (YAML)` truncate test failed")851 }852}853854func TestSpecificLanguages(t *testing.T) {855 languages := [...]string{856 "ABNF",857 "AL",858 "Alchemist",859 "Algol 68",860 "Alloy",861 "Amber",862 "Apex",863 "ArkTs",864 "Arturo",865 "Astro",866 "AWK",867 "BASH",868 "Bean",869 "Bicep",870 "Bitbucket Pipeline",871 "Blueprint",872 "Boo",873 "Bosque",874 "Bru",875 "C",876 "C3",877 "C Header",878 "C Shell",879 "C#",880 "C++",881 "C++ Header",882 "Cairo",883 "Cangjie",884 "Chapel",885 "Circom",886 "Clipper",887 "Clojure",888 "CMake",889 "Cuda",890 "Cypher",891 "D2",892 "DAML",893 "DM",894 "Docker ignore",895 "Dockerfile",896 "DOT",897 "Elixir Template",898 "Elm",899 "EmiT",900 "F#",901 "Factor",902 "Flow9",903 "FSL",904 "Futhark",905 "FXML",906 "Gemfile",907 "Gleam",908 "Go",909 "Go+",910 "Godot Scene",911 "GraphQL",912 "Gremlin",913 "Gwion",914 "HAML",915 "Hare",916 "Haskell",917 "HCL",918 "IEC61131-3",919 "ignore",920 "INI",921 "Java",922 "JavaScript",923 "JCL",924 "JSON5",925 "JSONC",926 "jq",927 "Korn Shell",928 "Kotlin",929 "Koto",930 "LALRPOP",931 "License",932 "Liquid",933 "LiveScript",934 "LLVM IR",935 "Lua",936 "Luau",937 "Luna",938 "MLIR",939 "Makefile",940 "Metal",941 "Mojo",942 "Monkey C",943 "Moonbit",944 "Move",945 "Nature",946 "Nushell",947 "OpenQASM",948 "OpenTofu",949 "Patch",950 "Perl",951 "PHP",952 "Pkl",953 "Plain Text",954 "POML",955 "PostScript",956 "ProGuard",957 "Proto",958 "Python",959 "Q#",960 "Qt Translation Source",961 "R",962 "Racket",963 "Rakefile",964 "RAML",965 "Rebol",966 "Redscript",967 "Rich Text Format",968 "Ruby",969 "Rust",970 "Scala",971 "Scallop",972 "Seed7",973 "Shell",974 "Sieve",975 "Slang",976 "Slint",977 "Smalltalk",978 "Snakemake",979 "Stan",980 "Swift",981 "Systemd",982 "Tact",983 "Teal",984 "Tera",985 "Templ",986 "Terraform",987 "TOML",988 "TOON",989 "TTCN-3",990 "Txtar",991 "TypeScript",992 "TypeSpec",993 "Typst",994 "Up",995 "Vala",996 "Vim Script",997 "Web Services Description Language",998 "WebGPU Enhanced Shading Language",999 "WebGPU Shading Language",1000 "wenyan",1001 "Wren",1002 "XHTML",1003 "XMake",1004 "XML Schema",1005 "YAML",1006 "Yarn",1007 "Zen C",1008 "Zig",1009 "ZoKrates",1010 "Zsh",1011 }10121013 output, err := runSCC("-f", "csv", "examples/language")1014 if err != nil {1015 t.Fatal(err)1016 }10171018 for _, language := range languages {1019 if !strings.Contains(output, language+",") {1020 t.Errorf("language not found in output: %v", language)1021 }1022 }1023}10241025// runHelp returns the `scc --help` text. --no-config keeps a developer's1026// ./.sccconfig out of it: runSCC reports combined output, so a warning from a1027// malformed one would be compared against the README as if it were help.1028func runHelp() (string, error) {1029 return runSCC("--help", "--no-config")1030}10311032var (1033 // cpuDefaultFlag matches the four flags defaulting to runtime.NumCPU(),1034 // the only ones whose default depends on the machine the paste was taken1035 // on. Every other default is a literal and stays under comparison.1036 cpuDefaultFlag = regexp.MustCompile(`^\s+--(file-list-job-workers|file-list-queue-size|file-process-job-workers|file-summary-job-queue-size) `)1037 helpDefault = regexp.MustCompile(`\(default \d+\)`)1038)10391040// normaliseHelp drops the parts of the help output that differ legitimately1041// between machines and releases: the version line and the CPU-count defaults.1042func normaliseHelp(s string) string {1043 var kept []string1044 for _, line := range strings.Split(strings.ReplaceAll(s, "\r\n", "\n"), "\n") {1045 if strings.HasPrefix(line, "Version ") {1046 continue1047 }1048 if cpuDefaultFlag.MatchString(line) {1049 line = helpDefault.ReplaceAllString(line, "(default N)")1050 }1051 kept = append(kept, line)1052 }1053 return strings.TrimSpace(strings.Join(kept, "\n"))1054}10551056// readmeHelpBlock returns the `scc -h` paste, which runs from the line after1057// the prompt down to the fence that closes the block.1058func readmeHelpBlock(t *testing.T, readme string) string {1059 t.Helper()1060 const anchor = "$ scc -h\n"1061 readme = strings.ReplaceAll(readme, "\r\n", "\n")1062 start := strings.Index(readme, anchor)1063 if start == -1 {1064 t.Fatal("README.md has no `$ scc -h` line to anchor the help paste to")1065 }1066 block := readme[start+len(anchor):]1067 end := strings.Index(block, "\n```")1068 if end == -1 {1069 t.Fatal("the `$ scc -h` block in README.md is never closed by a fence")1070 }1071 return block[:end]1072}10731074// TestReadmeHelpMatchesBinary holds the `scc -h` paste against the flags1075// cobra actually registers, so a new or reworded option cannot land with the1076// documented set left behind.1077func TestReadmeHelpMatchesBinary(t *testing.T) {1078 output, err := runHelp()1079 if err != nil {1080 t.Fatal(err)1081 }10821083 readme, err := os.ReadFile("README.md")1084 if err != nil {1085 t.Skipf("README.md is not readable, nothing to compare against: %v", err)1086 }10871088 want := normaliseHelp(output)1089 got := normaliseHelp(readmeHelpBlock(t, string(readme)))1090 if got == want {1091 return1092 }10931094 wantLines := strings.Split(want, "\n")1095 gotLines := strings.Split(got, "\n")1096 for i := 0; i < max(len(wantLines), len(gotLines)); i++ {1097 var w, g string1098 if i < len(wantLines) {1099 w = wantLines[i]1100 }1101 if i < len(gotLines) {1102 g = gotLines[i]1103 }1104 if w == g {1105 continue1106 }1107 t.Errorf("the `scc -h` block in README.md is out of date - paste the current `scc --help` output in under `$ scc -h`\nfirst difference at line %d:\n README: %s\n binary: %s", i+1, g, w)1108 return1109 }1110}11111112// reportSkipHelpRe pulls the recognised section names out of the1113// --report-skip line of `scc --help`, and backtickedRe the ones the README1114// table claims.1115var (1116 reportSkipHelpRe = regexp.MustCompile(`--report-skip string\s+[^(\n]+\(([a-z,]+)\)`)1117 backtickedRe = regexp.MustCompile("`([a-z]+)`")1118)11191120// TestReadmeReportSkipSections holds the --report-skip row of the report1121// options table against the names the flag itself lists, the two having1122// drifted apart once already.1123func TestReadmeReportSkipSections(t *testing.T) {1124 output, err := runHelp()1125 if err != nil {1126 t.Fatal(err)1127 }11281129 readme, err := os.ReadFile("README.md")1130 if err != nil {1131 t.Skipf("README.md is not readable, nothing to compare against: %v", err)1132 }11331134 sections := reportSkipHelpRe.FindStringSubmatch(output)1135 if sections == nil {1136 t.Fatal("no --report-skip section list found in `scc --help`")1137 }11381139 const prefix = "| `--report-skip LIST` |"1140 row := ""1141 for _, line := range strings.Split(string(readme), "\n") {1142 if strings.HasPrefix(line, prefix) {1143 row = line1144 break1145 }1146 }1147 if row == "" {1148 t.Fatalf("README.md has no %q row to check", prefix)1149 }11501151 recognised := map[string]bool{}1152 for _, name := range strings.Split(sections[1], ",") {1153 recognised[name] = true1154 if !strings.Contains(row, "`"+name+"`") {1155 t.Errorf("the --report-skip row in README.md does not list %q", name)1156 }1157 }11581159 cells := strings.Split(row, "|")1160 if len(cells) < 4 {1161 t.Fatalf("could not read a description cell out of %q", row)1162 }1163 for _, listed := range backtickedRe.FindAllStringSubmatch(cells[2], -1) {1164 if !recognised[listed[1]] {1165 t.Errorf("the --report-skip row in README.md lists %q, which the flag does not accept", listed[1])1166 }1167 }1168}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.
Generated by searchcode.com · powered by code_get_file + code_get_findings