Can cause issues on Windows consider filepath.Join instead
ignoreFileName := filepath.Join(tmpDir, ".gitignore")
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}451452func TestRecursivelyIgnore(t *testing.T) {453 tmpDir := t.TempDir()454 err := os.WriteFile(filepath.Join(tmpDir, ".gitignore"), []byte("ignore-git.txt\n"), 0644)455 if err != nil {456 t.Fatal(err)457 }458 err = os.WriteFile(filepath.Join(tmpDir, ".ignore"), []byte("vendor/\nignore.txt\n"), 0644)459 if err != nil {460 t.Fatal(err)461 }462 err = os.Mkdir(filepath.Join(tmpDir, "ignore"), 0755)463 if err != nil {464 t.Fatal(err)465 }466 err = os.WriteFile(filepath.Join(tmpDir, "ignore", "README.md"), []byte("Files in here are to ensure that .ignore and .gitignore work recursively\n"), 0644)467 if err != nil {468 t.Fatal(err)469 }470 err = os.WriteFile(filepath.Join(tmpDir, "ignore", "ignore.txt"), []byte("testing\n"), 0644)471 if err != nil {472 t.Fatal(err)473 }474 err = os.WriteFile(filepath.Join(tmpDir, "ignore", "ignore-git.txt"), []byte("git\ntesting\n"), 0644)475 if err != nil {476 t.Fatal(err)477 }478479 output, err := runSCC("--by-file", "--no-scc-ignore", tmpDir)480 if err != nil {481 t.Fatal(err)482 }483 if strings.Contains(output, "ignore.txt") || strings.Contains(output, "ignore-git.txt") {484 t.Errorf("ignore recursive filter failed, output:\n%s", output)485 }486487 output, err = runSCC("--by-file", "--no-scc-ignore", "--no-ignore", tmpDir)488 if err != nil {489 t.Fatal(err)490 }491 if !strings.Contains(output, "ignore.txt") || strings.Contains(output, "ignore-git.txt") {492 t.Errorf("ignore recursive filter failed, output:\n%s", output)493 }494495 output, err = runSCC("--by-file", "--no-scc-ignore", "--no-gitignore", tmpDir)496 if err != nil {497 t.Fatal(err)498 }499 if strings.Contains(output, "ignore.txt") || !strings.Contains(output, "ignore-git.txt") {500 t.Errorf("ignore recursive filter failed, output:\n%s", output)501 }502503 output, err = runSCC("--by-file", "--no-scc-ignore", "--no-ignore", "--no-gitignore", tmpDir)504 if err != nil {505 t.Fatal(err)506 }507 if !strings.Contains(output, "ignore.txt") || !strings.Contains(output, "ignore-git.txt") {508 t.Errorf("ignore recursive filter failed, output:\n%s", output)509 }510}511512func TestMultipleGitIgnore(t *testing.T) {513 tmpDir := t.TempDir()514 err := os.WriteFile(filepath.Join(tmpDir, ".gitignore"), []byte("ignore.txt\n"), 0644)515 if err != nil {516 t.Fatal(err)517 }518 err = os.Mkdir(filepath.Join(tmpDir, "ignore"), 0755)519 if err != nil {520 t.Fatal(err)521 }522 err = os.WriteFile(filepath.Join(tmpDir, "ignore", ".gitignore"), []byte("ignore.java\n"), 0644)523 if err != nil {524 t.Fatal(err)525 }526 err = os.WriteFile(filepath.Join(tmpDir, "ignore", "ignore.java"), []byte("//test\n"), 0644)527 if err != nil {528 t.Fatal(err)529 }530531 output, err := runSCC(tmpDir)532 if err != nil {533 t.Fatal(err)534 }535 if strings.Contains(output, "Java") {536 t.Fatalf("multiple gitignore failed, output:\n%s", output)537 }538}539540func TestFlagSuggestion(t *testing.T) {541 t.Parallel()542 testCases := []struct {543 args []string544 expectedOutput string545 }{546 {547 args: []string{"--farmat"},548 expectedOutput: "The most similar flag of --farmat is:\n\t--format\n",549 },550 {551 args: []string{"--no-gignore"},552 expectedOutput: "The most similar flags of --no-gignore are:\n\t--no-ignore\n\t--no-gitignore\n",553 },554 }555556 for _, tc := range testCases {557 output, err := runSCC(tc.args...)558 if err == nil {559 t.Fatal("scc should exit with error code")560 }561 if !strings.Contains(output, tc.expectedOutput) {562 t.Errorf("wrong suggestion for %v, want: %s, got: %s", tc.args, tc.expectedOutput, output)563 }564 }565}566567func TestDeterministicOutput(t *testing.T) {568 output, err := runSCC(".")569 if err != nil {570 t.Fatal(err)571 }572 for range 10 {573 output2, err := runSCC(".")574 if err != nil {575 t.Fatal(err)576 }577 if output != output2 {578 t.Fatalf("want:\n%s, got:\n%s", output, output2)579 }580 }581}582583func TestDuplicates(t *testing.T) {584 for range 10 {585 output, err := runSCC("-f", "json", "-d", "./examples/duplicates/")586 if err != nil {587 t.Fatal(err)588 }589 if !strings.Contains(output, `"Count":1`) {590 t.Fatalf("duplicates check failed, output:\n%s", output)591 }592 }593}594595func TestCountAs(t *testing.T) {596 testCases := []struct {597 countAs string598 expected []string599 }{600 {601 countAs: "jsp:html",602 expected: []string{"HTML"},603 },604 {605 countAs: "JsP:html",606 expected: []string{"HTML"},607 },608 {609 countAs: "jsp:j2",610 expected: []string{"Jinja"},611 },612 {613 countAs: "jsp:html,new:java",614 expected: []string{"HTML", "Java"},615 },616 {617 countAs: "jsp:html,new:C Header",618 expected: []string{"HTML", "C Header"},619 },620 }621622 for _, tc := range testCases {623 output, err := runSCC("-f", "csv", "--count-as", tc.countAs, "./examples/countas/")624 if err != nil {625 t.Fatal(err)626 }627 for _, expectedLang := range tc.expected {628 if !strings.Contains(output, expectedLang+",") {629 t.Errorf("count as failed, count as: %s, output:\n%s", tc.countAs, output)630 }631 }632 }633}634635func TestCountAsPattern(t *testing.T) {636 // foo_spec.rb is relabelled to the new Ruby Spec category, app.rb stays Ruby637 output, err := runSCC("-f", "csv", "--count-as-pattern", "glob:*_spec.rb:Ruby Spec:Ruby", "./examples/countaspattern/")638 if err != nil {639 t.Fatal(err)640 }641642 // The minted category row, with non-zero comment and complexity counts which643 // proves the counting rules were cloned from the Ruby base language644 if !strings.Contains(output, "Ruby Spec,9,7,1,1,1,") {645 t.Errorf("count-as-pattern failed to produce Ruby Spec row, output:\n%s", output)646 }647 // The non matching file is still counted as plain Ruby648 if !strings.Contains(output, "Ruby,7,5,1,1,1,") {649 t.Errorf("count-as-pattern should leave app.rb as Ruby, output:\n%s", output)650 }651652 // The regex engine should produce the same relabelling653 output, err = runSCC("-f", "csv", "--count-as-pattern", `re:_spec\.rb$:Ruby Spec:Ruby`, "./examples/countaspattern/")654 if err != nil {655 t.Fatal(err)656 }657 if !strings.Contains(output, "Ruby Spec,") {658 t.Errorf("regex count-as-pattern failed, output:\n%s", output)659 }660}661662func TestCountAsPatternInvalidSkipped(t *testing.T) {663 // An unresolvable base language is reported and skipped, the run still works664 output, err := runSCC("-f", "csv", "--count-as-pattern", "glob:*_spec.rb:Ruby Spec:Nonexistent", "./examples/countaspattern/")665 if err != nil {666 t.Fatal(err)667 }668 if !strings.Contains(output, "is not a known language or extension") {669 t.Errorf("expected error message for unknown base language, output:\n%s", output)670 }671 if strings.Contains(output, "Ruby Spec,") {672 t.Errorf("Ruby Spec should not appear when the rule was skipped, output:\n%s", output)673 }674}675676func TestRemapUnknown(t *testing.T) {677 t.Parallel()678 output, err := runSCC("-f", "csv", "--remap-unknown", "-*- C++ -*-:C Header", "./examples/remap/unknown")679 if err != nil {680 t.Fatal(err)681 }682 if !strings.Contains(output, "C Header,") {683 t.Fatalf("remap unknown failed, output:\n%s", output)684 }685}686687func TestRemapAll(t *testing.T) {688 t.Parallel()689 output, err := runSCC("-f", "csv", "--remap-all", "-*- C++ -*-:C Header", "./examples/remap/java.java")690 if err != nil {691 t.Fatal(err)692 }693 if !strings.Contains(output, "C Header,") {694 t.Fatalf("remap all failed, output:\n%s", output)695 }696}697698func TestCocomoProjectType(t *testing.T) {699 projectTypes := []string{"organic", "semi-detached", "embedded", "custom,1,1,1,1"}700 for _, typ := range projectTypes {701 output, err := runSCC("--cocomo-project-type", typ)702 if err != nil {703 t.Fatal(err)704 }705 if !strings.Contains(output, fmt.Sprintf("Estimated Cost to Develop (%s)", typ)) ||706 !strings.Contains(output, fmt.Sprintf("Estimated Schedule Effort (%s)", typ)) ||707 !strings.Contains(output, fmt.Sprintf("Estimated People Required (%s)", typ)) {708 t.Errorf("check cocomo project type failed: %s", typ)709 }710 }711}712713func TestCocomoProjectTypeFallback(t *testing.T) {714 unknownTypes := []string{"doesnotexist", "custom,1,1,1"}715 for _, typ := range unknownTypes {716 output, err := runSCC("--cocomo-project-type", typ)717 if err != nil {718 t.Fatal(err)719 }720 if !strings.Contains(output, "Estimated Cost to Develop (organic)") ||721 !strings.Contains(output, "Estimated Schedule Effort (organic)") ||722 !strings.Contains(output, "Estimated People Required (organic)") {723 t.Errorf("check cocomo project type fallback failed: %s", typ)724 }725 }726}727728func TestOutputBytes(t *testing.T) {729 jsonOutput, err := runSCC("-f", "json")730 if err != nil {731 t.Fatal(err)732 }733 if !strings.Contains(jsonOutput, `"Bytes":`) {734 t.Errorf("json output does not contain `Bytes` field, output:\n%s", jsonOutput)735 }736737 output, err := runSCC()738 if err != nil {739 t.Fatal(err)740 }741 if !strings.Contains(output, "megabytes") {742 t.Errorf("output does not contain `megabytes`, output:\n%s", output)743 }744}745746func TestFileGCCount(t *testing.T) {747 const target = "./examples/duplicates"748 files, err := os.ReadDir(target)749 if err != nil {750 t.Fatal(err)751 }752753 output, err := runSCC("--file-gc-count", strconv.Itoa(len(files)-1), "-v", target)754 if err != nil {755 t.Fatal(err)756 }757 if !strings.Contains(output, "read file limit exceeded GC re-enabled") {758 t.Errorf("test file GC count failed, file count: %d, limit: %d", len(files), len(files)-1)759 }760761 output, err = runSCC("--file-gc-count", strconv.Itoa(len(files)+1), "-v", target)762 if err != nil {763 t.Fatal(err)764 }765 if strings.Contains(output, "read file limit exceeded GC re-enabled") {766 t.Errorf("test file GC count failed, file count: %d, limit: %d", len(files), len(files)+1)767 }768}769770func TestIncludeSymlinks(t *testing.T) {771 if runtime.GOOS == "windows" {772 t.Skip("skipping symlink test on Windows due to privilege requirements")773 }774775 tmpDir, err := filepath.EvalSymlinks(t.TempDir())776 if err != nil {777 t.Fatal(err)778 }779780 dirA := filepath.Join(tmpDir, "a")781 dirB := filepath.Join(tmpDir, "b")782 if err := os.Mkdir(dirA, 0755); err != nil {783 t.Fatal(err)784 }785 if err := os.Mkdir(dirB, 0755); err != nil {786 t.Fatal(err)787 }788789 const fileName = "source.go"790 if err := os.WriteFile(filepath.Join(dirA, fileName), []byte("package main\n"), 0644); err != nil {791 t.Fatal(err)792 }793 if err := os.WriteFile(filepath.Join(dirB, fileName), []byte("package main\n"), 0644); err != nil {794 t.Fatal(err)795 }796 // link to another dir, should be counted under --include-symlinks797 if err := os.Symlink(filepath.Join(dirB, fileName), filepath.Join(dirA, "link1.go")); err != nil {798 t.Fatal(err)799 }800 // link to the same dir, this should be ignored in all times801 if err := os.Symlink(filepath.Join(dirA, fileName), filepath.Join(dirA, "link2.go")); err != nil {802 t.Fatal(err)803 }804805 output, err := runSCC("-f", "json", "--no-scc-ignore", dirA)806 if err != nil {807 t.Fatal(err)808 }809 if !strings.Contains(output, `"Count":1`) {810 t.Errorf("count without symlink failed, output:\n%s", output)811 }812813 output, err = runSCC("-f", "json", "--no-scc-ignore", "--include-symlinks", dirA)814 if err != nil {815 t.Fatal(err)816 }817 if !strings.Contains(output, `"Count":2`) {818 t.Errorf("count includes symlink failed, output:\n%s", output)819 }820}821822func TestLanguageNameTruncate(t *testing.T) {823 output, err := runSCC("examples/language")824 if err != nil {825 t.Fatal(err)826 }827 if strings.Count(output, "Bitbucket Pipe…") != 1 {828 t.Errorf("`Bitbucket Pipeline` truncate test failed")829 }830 if strings.Count(output, "CloudFormation…") != 2 {831 t.Errorf("`CloudFormation (JSON)` and `CloudFormation (YAML)` truncate test failed")832 }833}834835func TestSpecificLanguages(t *testing.T) {836 languages := [...]string{837 "ABNF",838 "AL",839 "Alchemist",840 "Algol 68",841 "Alloy",842 "Amber",843 "Apex",844 "ArkTs",845 "Arturo",846 "Astro",847 "AWK",848 "BASH",849 "Bean",850 "Bicep",851 "Bitbucket Pipeline",852 "Blueprint",853 "Boo",854 "Bosque",855 "Bru",856 "C",857 "C3",858 "C Header",859 "C Shell",860 "C#",861 "C++",862 "C++ Header",863 "Cairo",864 "Cangjie",865 "Chapel",866 "Circom",867 "Clipper",868 "Clojure",869 "CMake",870 "Cuda",871 "Cypher",872 "D2",873 "DAML",874 "DM",875 "Docker ignore",876 "Dockerfile",877 "DOT",878 "Elixir Template",879 "Elm",880 "EmiT",881 "F#",882 "Factor",883 "Flow9",884 "FSL",885 "Futhark",886 "FXML",887 "Gemfile",888 "Gleam",889 "Go",890 "Go+",891 "Godot Scene",892 "GraphQL",893 "Gremlin",894 "Gwion",895 "HAML",896 "Hare",897 "Haskell",898 "HCL",899 "IEC61131-3",900 "ignore",901 "INI",902 "Java",903 "JavaScript",904 "JCL",905 "JSON5",906 "JSONC",907 "jq",908 "Korn Shell",909 "Koto",910 "LALRPOP",911 "License",912 "LiveScript",913 "LLVM IR",914 "Lua",915 "Luau",916 "Luna",917 "MLIR",918 "Makefile",919 "Metal",920 "Mojo",921 "Monkey C",922 "Moonbit",923 "Move",924 "Nature",925 "Nushell",926 "OpenQASM",927 "OpenTofu",928 "Patch",929 "Perl",930 "Pkl",931 "Plain Text",932 "POML",933 "PostScript",934 "Proto",935 "Python",936 "Q#",937 "R",938 "Racket",939 "Rakefile",940 "RAML",941 "Rebol",942 "Redscript",943 "Rich Text Format",944 "Scallop",945 "Seed7",946 "Shell",947 "Sieve",948 "Slang",949 "Slint",950 "Smalltalk",951 "Snakemake",952 "Stan",953 "Systemd",954 "Tact",955 "Teal",956 "Tera",957 "Templ",958 "Terraform",959 "TOML",960 "TOON",961 "TTCN-3",962 "TypeScript",963 "TypeSpec",964 "Typst",965 "Up",966 "Vala",967 "Vim Script",968 "Web Services Description Language",969 "WebGPU Enhanced Shading Language",970 "WebGPU Shading Language",971 "wenyan",972 "Wren",973 "XHTML",974 "XMake",975 "XML Schema",976 "YAML",977 "Yarn",978 "Zen C",979 "Zig",980 "ZoKrates",981 "Zsh",982 }983984 output, err := runSCC("-f", "csv", "examples/language")985 if err != nil {986 t.Fatal(err)987 }988989 for _, language := range languages {990 if !strings.Contains(output, language+",") {991 t.Errorf("language not found in output: %v", language)992 }993 }994}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.