main_test.go GO 995 lines View on github.com → Search inside
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}

Code quality findings 31

Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
ignoreFileName := filepath.Join(tmpDir, ".gitignore")
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
xmlFileName := filepath.Join(tmpDir, "ignored.xml")
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
flagsFileName := filepath.Join(tmpDir, "flags.txt")
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
err := os.WriteFile(flagsFileName, []byte(tc), 0644)
Regexp compiled inside function; compile once at package level to avoid recompilation on each call
info performance regexp-compile-in-func
tabularPattern := regexp.MustCompile(`Processed .+? bytes, .+? megabytes \(SI\)`)
Regexp compiled inside function; compile once at package level to avoid recompilation on each call
info performance regexp-compile-in-func
sqlPattern := regexp.MustCompile(`insert into t values\(.+?\);`)
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
outputTabular := filepath.Join(tmpDir, "output.tab")
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
outputWide := filepath.Join(tmpDir, "output.wide")
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
outputJSON1 := filepath.Join(tmpDir, "output.json")
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
outputJSON2 := filepath.Join(tmpDir, "output2.json")
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
outputCSV := filepath.Join(tmpDir, "output.csv")
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
outputYAML := filepath.Join(tmpDir, "output.yaml")
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
outputHTML := filepath.Join(tmpDir, "output.html")
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
outputHTMLTable := filepath.Join(tmpDir, "output_table.html")
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
outputSQL := filepath.Join(tmpDir, "output.sql")
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
err := os.WriteFile(filepath.Join(tmpDir, ".gitignore"), []byte("ignore-git.txt\n"), 0644)
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
err = os.WriteFile(filepath.Join(tmpDir, ".ignore"), []byte("vendor/\nignore.txt\n"), 0644)
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
err = os.Mkdir(filepath.Join(tmpDir, "ignore"), 0755)
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
err = os.WriteFile(filepath.Join(tmpDir, "ignore", "README.md"), []byte("Files in here are to ensure that .ignore and .gitignore work recursively\n"), 0644)
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
err = os.WriteFile(filepath.Join(tmpDir, "ignore", "ignore.txt"), []byte("testing\n"), 0644)
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
err = os.WriteFile(filepath.Join(tmpDir, "ignore", "ignore-git.txt"), []byte("git\ntesting\n"), 0644)
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
err := os.WriteFile(filepath.Join(tmpDir, ".gitignore"), []byte("ignore.txt\n"), 0644)
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
err = os.Mkdir(filepath.Join(tmpDir, "ignore"), 0755)
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
err = os.WriteFile(filepath.Join(tmpDir, "ignore", ".gitignore"), []byte("ignore.java\n"), 0644)
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
err = os.WriteFile(filepath.Join(tmpDir, "ignore", "ignore.java"), []byte("//test\n"), 0644)
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
dirA := filepath.Join(tmpDir, "a")
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
dirB := filepath.Join(tmpDir, "b")
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
if err := os.WriteFile(filepath.Join(dirA, fileName), []byte("package main\n"), 0644); err != nil {
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
if err := os.WriteFile(filepath.Join(dirB, fileName), []byte("package main\n"), 0644); err != nil {
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
if err := os.Symlink(filepath.Join(dirB, fileName), filepath.Join(dirA, "link1.go")); err != nil {
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
if err := os.Symlink(filepath.Join(dirA, fileName), filepath.Join(dirA, "link2.go")); err != nil {

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.