main_test.go GO 989 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	res, err := cmd.CombinedOutput()35	return string(res), err36}3738func TestNoGitIgnore(t *testing.T) {39	tmpDir := t.TempDir()40	ignoreFileName := filepath.Join(tmpDir, ".gitignore")41	err := os.WriteFile(ignoreFileName, []byte("ignored.xml\n"), 0644)42	if err != nil {43		t.Fatal(err)44	}45	xmlFileName := filepath.Join(tmpDir, "ignored.xml")46	err = os.WriteFile(xmlFileName, []byte(`<?xml version="1.0" encoding="UTF-8"?>`), 0644)47	if err != nil {48		t.Fatal(err)49	}5051	output, err := runSCC(tmpDir)52	if err != nil {53		t.Fatal(err)54	}55	if strings.Contains(output, "XML") {56		t.Fatalf("test --no-gitignore failed, output:\n%s", output)57	}5859	output, err = runSCC("--no-gitignore", tmpDir)60	if err != nil {61		t.Fatal(err)62	}63	if !strings.Contains(output, "XML") {64		t.Fatalf("test --no-gitignore failed, output:\n%s", output)65	}66}6768func TestIssue82(t *testing.T) {69	t.Parallel()70	// Regression issue https://github.com/boyter/scc/issues/8271	output1, err := runSCC(".")72	if err != nil {73		t.Fatal(err)74	}7576	pwd, err := os.Getwd()77	if err != nil {78		t.Fatal(err)79	}80	output2, err := runSCC(pwd)81	if err != nil {82		t.Fatal(err)83	}8485	if output1 != output2 {86		t.Fatalf("`./scc .` not equal to `./scc ${PWD}`")87	}88}8990func TestIncludeExt(t *testing.T) {91	t.Parallel()92	// Regression issue https://github.com/boyter/scc/issues/10893	output, err := runSCC("--include-ext", "go", "examples/language")94	if err != nil {95		t.Fatal(err)96	}97	if !strings.Contains(output, "Go") || strings.Contains(output, "Java") {98		t.Fatalf("include-ext check failed, output:\n%s", output)99	}100}101102func TestIssue115(t *testing.T) {103	t.Parallel()104	// Regression issue https://github.com/boyter/scc/issues/115105	output, err := runSCC("examples/issue115/.test/file")106	if err != nil {107		t.Fatal(err)108	}109	if strings.Contains(output, "Perl") {110		t.Fatalf("Should not print Perl, output:\n%s", output)111	}112}113114func TestIssue120(t *testing.T) {115	t.Parallel()116	// Regression issue https://github.com/boyter/scc/issues/120117	output, err := runSCC("-i", "java", "./examples/issue120")118	if err != nil {119		t.Fatal(err)120	}121	if strings.Contains(output, "Perl") {122		t.Fatal("extension param should ignore Shebang")123	}124}125126func TestIssue152(t *testing.T) {127	t.Parallel()128	// Regression issue https://github.com/boyter/scc/issues/152129	output, err := runSCC("-i", "css", "./examples/issue152/")130	if err != nil {131		t.Fatal(err)132	}133	if !strings.Contains(output, "CSS") {134		t.Fatalf("`-i css` extension check failed, output:\n%s", output)135	}136}137138func TestIssue250(t *testing.T) {139	// Regression issue https://github.com/boyter/scc/issues/250140	output1, err := runSCC("--exclude-dir", "examples/")141	if err != nil {142		t.Fatal(err)143	}144	output2, err := runSCC("--exclude-dir", "examples")145	if err != nil {146		t.Fatal(err)147	}148149	if output1 != output2 {150		t.Fatalf("examples exclude-dir check failed, output1:\n%s, output2:\n%s", output1, output2)151	}152}153154func TestIssue259(t *testing.T) {155	// Regression issue https://github.com/boyter/scc/issues/259156	output, err := runSCC("-f", "csv", "--exclude-ext", "go")157	if err != nil {158		t.Fatal(err)159	}160161	if strings.Contains(output, "Go,") {162		t.Fatalf("exclude-ext check failed, output:\n%s", output)163	}164}165166func TestIssue260(t *testing.T) {167	t.Parallel()168	// Regression issue https://github.com/boyter/scc/issues/260169	_, err := runSCC("-d", "examples/issue260/")170	if err != nil {171		t.Fatalf("duplicate empty crash: %v", err)172	}173}174175func TestIssue345(t *testing.T) {176	t.Parallel()177	// Regression issue https://github.com/boyter/scc/issues/345178	const expectedOutput = "C++,4,3,1,0,0,76,1,0"179	output, err := runSCC("-f", "csv", "--no-scc-ignore", "examples/issue345/")180	if err != nil {181		t.Fatal(err)182	}183	lines := strings.Split(output, "\n")184	if len(lines) < 2 {185		t.Fatalf("wrong output: %s", output)186	}187	if lines[1] != expectedOutput {188		t.Fatalf("got: %s, want: %s", lines[1], expectedOutput)189	}190}191192func TestIssue379(t *testing.T) {193	t.Parallel()194	// Regression issue https://github.com/boyter/scc/issues/379195	const expectedOutput = "Python,7,4,2,1,1,83,1,0"196	output, err := runSCC("-f", "csv", "--no-scc-ignore", "examples/issue379/")197	if err != nil {198		t.Fatal(err)199	}200	lines := strings.Split(output, "\n")201	if len(lines) < 2 {202		t.Fatalf("wrong output: %s", output)203	}204	if lines[1] != expectedOutput {205		t.Fatalf("got: %s, want: %s", lines[1], expectedOutput)206	}207}208209func TestIssue457(t *testing.T) {210	t.Parallel()211	// Regression issue https://github.com/boyter/scc/issues/457212	output, err := runSCC("-M", ".*")213	if err != nil {214		t.Fatal(err)215	}216	if !strings.Contains(output, "0.000 megabytes") {217		t.Fatalf("Issue 457 test failed, output:\n%s", output)218	}219}220221func TestIssue564(t *testing.T) {222	t.Parallel()223	// Regression issue https://github.com/boyter/scc/issues/564224	const expectedPythonOutput = "Python,3,3,0,0,0,84,3,0"225	const expectedGoOutput = "Go,6,4,0,2,0,58,2,0"226	output, err := runSCC("-f", "csv", "--no-scc-ignore", "examples/issue564/")227	if err != nil {228		t.Fatal(err)229	}230	lines := strings.Split(output, "\n")231	if len(lines) < 3 {232		t.Fatalf("wrong output: %s", output)233	}234	if lines[1] != expectedPythonOutput {235		t.Fatalf("got: %s, want: %s", lines[1], expectedPythonOutput)236	}237	if lines[2] != expectedGoOutput {238		t.Fatalf("got: %s, want: %s", lines[2], expectedGoOutput)239	}240}241242func TestIssue610(t *testing.T) {243	t.Parallel()244	// Regression issue https://github.com/boyter/scc/issues/610245	const expectedOutput = "TypeScript,11,7,2,2,1,214,1,0"246	output, err := runSCC("-f", "csv", "--no-scc-ignore", "examples/issue610/")247	if err != nil {248		t.Fatal(err)249	}250	lines := strings.Split(output, "\n")251	if len(lines) < 2 {252		t.Fatalf("wrong output: %s", output)253	}254	if lines[1] != expectedOutput {255		t.Fatalf("got: %s, want: %s", lines[1], expectedOutput)256	}257}258259func TestIssue339(t *testing.T) {260	t.Parallel()261	// Regression issue https://github.com/boyter/scc/issues/339262	output, err := runSCC("-f", "csv", "--no-scc-ignore", "examples/issue339/")263	if err != nil {264		t.Fatal(err)265	}266	if !strings.Contains(output, "MATLAB") {267		t.Errorf("can not find MATLAB, output: %s", output)268	}269	if !strings.Contains(output, "Objective C") {270		t.Errorf("can not find Objective C, output:\n%s", output)271	}272}273274func TestInvalidOption(t *testing.T) {275	t.Parallel()276	output, err := runSCC("--not-a-real-option")277	if err == nil {278		t.Fatal("scc should exit with error code")279	}280	if !strings.Contains(output, "Error: unknown flag: --not-a-real-option") {281		t.Fatalf("scc should report invalid options, output:\n%s", output)282	}283}284285func TestFileFlagSyntax(t *testing.T) {286	tmpDir := t.TempDir()287	flagsFileName := filepath.Join(tmpDir, "flags.txt")288	// include \n, \r\n and no line terminators289	testCases := []string{290		"go.mod\ngo.sum\nLICENSE\n",291		"go.mod\r\ngo.sum\r\nLICENSE\r\n",292		"go.mod\ngo.sum\nLICENSE",293		"go.mod\r\ngo.sum\r\nLICENSE",294		"go.mod\ngo.sum\r\nLICENSE",295	}296297	for _, tc := range testCases {298		err := os.WriteFile(flagsFileName, []byte(tc), 0644)299		if err != nil {300			t.Fatal(err)301		}302		_, err = runSCC("@" + flagsFileName)303		if err != nil {304			t.Errorf("flag syntax faild: %q, %v", tc, err)305		}306	}307}308309func TestLineLength(t *testing.T) {310	t.Parallel()311	output, err := runSCC("-m")312	if err != nil {313		t.Fatal(err)314	}315	if strings.Count(output, "MaxLine / MeanLine") < 2 {316		t.Fatalf("line length test failed, output:\n%s", output)317	}318}319320func TestFormatHTML(t *testing.T) {321	t.Parallel()322	output, err := runSCC("--format", "html")323	if err != nil {324		t.Fatal(err)325	}326	if !strings.Contains(output, "<title>scc html output</title>") {327		t.Fatalf("html format test failed, output:\n%s", output)328	}329}330331func TestFormatHTMLTable(t *testing.T) {332	t.Parallel()333	output, err := runSCC("--format", "html-table")334	if err != nil {335		t.Fatal(err)336	}337	if !strings.Contains(output, `<table id="scc-table">`) {338		t.Fatalf("html-table format test failed, output:\n%s", output)339	}340}341342func TestFormatSQL(t *testing.T) {343	t.Parallel()344	output, err := runSCC("--format", "sql")345	if err != nil {346		t.Fatal(err)347	}348	if !strings.Contains(output, "create table metadata (   -- github.com/boyter/scc") {349		t.Fatalf("sql format test failed, output:\n%s", output)350	}351}352353func TestFormatSQLInsert(t *testing.T) {354	t.Parallel()355	output, err := runSCC("--format", "sql-insert")356	if err != nil {357		t.Fatal(err)358	}359	if !strings.Contains(output, "begin transaction;\ninsert into t values(") {360		t.Fatalf("sql-insert format test failed, output:\n%s", output)361	}362}363364func TestMultipleFormatStdout(t *testing.T) {365	output, err := runSCC("--format-multi", "tabular:stdout,html:stdout,csv:stdout,sql:stdout")366	if err != nil {367		t.Fatal(err)368	}369370	tabularPattern := regexp.MustCompile(`Processed .+? bytes, .+? megabytes \(SI\)`)371	if !tabularPattern.MatchString(output) {372		t.Errorf("multi-format tabular failed, output:\n%s", output)373	}374375	if !strings.Contains(output, `<html lang="en"><head><meta charset="utf-8" /><title>scc html output</title>`) {376		t.Errorf("multi-format html failed, output:\n%s", output)377	}378379	if !strings.Contains(output, "Language,Lines,Code,Comments,Blanks,Complexity,Bytes,Files,ULOC") {380		t.Errorf("multi-format csv failed, output:\n%s", output)381	}382383	sqlPattern := regexp.MustCompile(`insert into t values\(.+?\);`)384	if !sqlPattern.MatchString(output) {385		t.Errorf("multi-format sql failed, output:\n%s", output)386	}387}388389func TestMultipleFormatWriteFile(t *testing.T) {390	tmpDir := t.TempDir()391	outputTabular := filepath.Join(tmpDir, "output.tab")392	outputWide := filepath.Join(tmpDir, "output.wide")393	outputJSON1 := filepath.Join(tmpDir, "output.json")394	outputJSON2 := filepath.Join(tmpDir, "output2.json")395	outputCSV := filepath.Join(tmpDir, "output.csv")396	outputYAML := filepath.Join(tmpDir, "output.yaml")397	outputHTML := filepath.Join(tmpDir, "output.html")398	outputHTMLTable := filepath.Join(tmpDir, "output_table.html")399	outputSQL := filepath.Join(tmpDir, "output.sql")400401	multiFormatArgs := fmt.Sprintf(402		"tabular:%s,wide:%s,json:%s,json2:%s,csv:%s,cloc-yaml:%s,html:%s,html-table:%s,sql:%s",403		outputTabular,404		outputWide,405		outputJSON1,406		outputJSON2,407		outputCSV,408		outputYAML,409		outputHTML,410		outputHTMLTable,411		outputSQL,412	)413414	_, err := runSCC("--format-multi", multiFormatArgs)415	if err != nil {416		t.Fatal(err)417	}418419	if info, err := os.Stat(outputTabular); err != nil || info.Size() <= 0 {420		t.Fatal("tabular write file test failed")421	}422	if info, err := os.Stat(outputWide); err != nil || info.Size() <= 0 {423		t.Fatal("wide write file test failed")424	}425	if info, err := os.Stat(outputJSON1); err != nil || info.Size() <= 0 {426		t.Fatal("json write file test failed")427	}428	if info, err := os.Stat(outputJSON2); err != nil || info.Size() <= 0 {429		t.Fatal("json2 write file test failed")430	}431	if info, err := os.Stat(outputCSV); err != nil || info.Size() <= 0 {432		t.Fatal("csv write file test failed")433	}434	if info, err := os.Stat(outputYAML); err != nil || info.Size() <= 0 {435		t.Fatal("cloc-yaml write file test failed")436	}437	if info, err := os.Stat(outputHTML); err != nil || info.Size() <= 0 {438		t.Fatal("html write file test failed")439	}440	if info, err := os.Stat(outputHTMLTable); err != nil || info.Size() <= 0 {441		t.Fatal("html-table write file test failed")442	}443	if info, err := os.Stat(outputSQL); err != nil || info.Size() <= 0 {444		t.Fatal("sql write file test failed")445	}446}447448func TestRecursivelyIgnore(t *testing.T) {449	tmpDir := t.TempDir()450	err := os.WriteFile(filepath.Join(tmpDir, ".gitignore"), []byte("ignore-git.txt\n"), 0644)451	if err != nil {452		t.Fatal(err)453	}454	err = os.WriteFile(filepath.Join(tmpDir, ".ignore"), []byte("vendor/\nignore.txt\n"), 0644)455	if err != nil {456		t.Fatal(err)457	}458	err = os.Mkdir(filepath.Join(tmpDir, "ignore"), 0755)459	if err != nil {460		t.Fatal(err)461	}462	err = os.WriteFile(filepath.Join(tmpDir, "ignore", "README.md"), []byte("Files in here are to ensure that .ignore and .gitignore work recursively\n"), 0644)463	if err != nil {464		t.Fatal(err)465	}466	err = os.WriteFile(filepath.Join(tmpDir, "ignore", "ignore.txt"), []byte("testing\n"), 0644)467	if err != nil {468		t.Fatal(err)469	}470	err = os.WriteFile(filepath.Join(tmpDir, "ignore", "ignore-git.txt"), []byte("git\ntesting\n"), 0644)471	if err != nil {472		t.Fatal(err)473	}474475	output, err := runSCC("--by-file", "--no-scc-ignore", tmpDir)476	if err != nil {477		t.Fatal(err)478	}479	if strings.Contains(output, "ignore.txt") || strings.Contains(output, "ignore-git.txt") {480		t.Errorf("ignore recursive filter failed, output:\n%s", output)481	}482483	output, err = runSCC("--by-file", "--no-scc-ignore", "--no-ignore", tmpDir)484	if err != nil {485		t.Fatal(err)486	}487	if !strings.Contains(output, "ignore.txt") || strings.Contains(output, "ignore-git.txt") {488		t.Errorf("ignore recursive filter failed, output:\n%s", output)489	}490491	output, err = runSCC("--by-file", "--no-scc-ignore", "--no-gitignore", tmpDir)492	if err != nil {493		t.Fatal(err)494	}495	if strings.Contains(output, "ignore.txt") || !strings.Contains(output, "ignore-git.txt") {496		t.Errorf("ignore recursive filter failed, output:\n%s", output)497	}498499	output, err = runSCC("--by-file", "--no-scc-ignore", "--no-ignore", "--no-gitignore", tmpDir)500	if err != nil {501		t.Fatal(err)502	}503	if !strings.Contains(output, "ignore.txt") || !strings.Contains(output, "ignore-git.txt") {504		t.Errorf("ignore recursive filter failed, output:\n%s", output)505	}506}507508func TestMultipleGitIgnore(t *testing.T) {509	tmpDir := t.TempDir()510	err := os.WriteFile(filepath.Join(tmpDir, ".gitignore"), []byte("ignore.txt\n"), 0644)511	if err != nil {512		t.Fatal(err)513	}514	err = os.Mkdir(filepath.Join(tmpDir, "ignore"), 0755)515	if err != nil {516		t.Fatal(err)517	}518	err = os.WriteFile(filepath.Join(tmpDir, "ignore", ".gitignore"), []byte("ignore.java\n"), 0644)519	if err != nil {520		t.Fatal(err)521	}522	err = os.WriteFile(filepath.Join(tmpDir, "ignore", "ignore.java"), []byte("//test\n"), 0644)523	if err != nil {524		t.Fatal(err)525	}526527	output, err := runSCC(tmpDir)528	if err != nil {529		t.Fatal(err)530	}531	if strings.Contains(output, "Java") {532		t.Fatalf("multiple gitignore failed, output:\n%s", output)533	}534}535536func TestFlagSuggestion(t *testing.T) {537	t.Parallel()538	testCases := []struct {539		args           []string540		expectedOutput string541	}{542		{543			args:           []string{"--farmat"},544			expectedOutput: "The most similar flag of --farmat is:\n\t--format\n",545		},546		{547			args:           []string{"--no-gignore"},548			expectedOutput: "The most similar flags of --no-gignore are:\n\t--no-ignore\n\t--no-gitignore\n",549		},550	}551552	for _, tc := range testCases {553		output, err := runSCC(tc.args...)554		if err == nil {555			t.Fatal("scc should exit with error code")556		}557		if !strings.Contains(output, tc.expectedOutput) {558			t.Errorf("wrong suggestion for %v, want: %s, got: %s", tc.args, tc.expectedOutput, output)559		}560	}561}562563func TestDeterministicOutput(t *testing.T) {564	output, err := runSCC(".")565	if err != nil {566		t.Fatal(err)567	}568	for range 10 {569		output2, err := runSCC(".")570		if err != nil {571			t.Fatal(err)572		}573		if output != output2 {574			t.Fatalf("want:\n%s, got:\n%s", output, output2)575		}576	}577}578579func TestDuplicates(t *testing.T) {580	for range 10 {581		output, err := runSCC("-f", "json", "-d", "./examples/duplicates/")582		if err != nil {583			t.Fatal(err)584		}585		if !strings.Contains(output, `"Count":1`) {586			t.Fatalf("duplicates check failed, output:\n%s", output)587		}588	}589}590591func TestCountAs(t *testing.T) {592	testCases := []struct {593		countAs  string594		expected []string595	}{596		{597			countAs:  "jsp:html",598			expected: []string{"HTML"},599		},600		{601			countAs:  "JsP:html",602			expected: []string{"HTML"},603		},604		{605			countAs:  "jsp:j2",606			expected: []string{"Jinja"},607		},608		{609			countAs:  "jsp:html,new:java",610			expected: []string{"HTML", "Java"},611		},612		{613			countAs:  "jsp:html,new:C Header",614			expected: []string{"HTML", "C Header"},615		},616	}617618	for _, tc := range testCases {619		output, err := runSCC("-f", "csv", "--count-as", tc.countAs, "./examples/countas/")620		if err != nil {621			t.Fatal(err)622		}623		for _, expectedLang := range tc.expected {624			if !strings.Contains(output, expectedLang+",") {625				t.Errorf("count as failed, count as: %s, output:\n%s", tc.countAs, output)626			}627		}628	}629}630631func TestCountAsPattern(t *testing.T) {632	// foo_spec.rb is relabelled to the new Ruby Spec category, app.rb stays Ruby633	output, err := runSCC("-f", "csv", "--count-as-pattern", "glob:*_spec.rb:Ruby Spec:Ruby", "./examples/countaspattern/")634	if err != nil {635		t.Fatal(err)636	}637638	// The minted category row, with non-zero comment and complexity counts which639	// proves the counting rules were cloned from the Ruby base language640	if !strings.Contains(output, "Ruby Spec,9,7,1,1,1,") {641		t.Errorf("count-as-pattern failed to produce Ruby Spec row, output:\n%s", output)642	}643	// The non matching file is still counted as plain Ruby644	if !strings.Contains(output, "Ruby,7,5,1,1,1,") {645		t.Errorf("count-as-pattern should leave app.rb as Ruby, output:\n%s", output)646	}647648	// The regex engine should produce the same relabelling649	output, err = runSCC("-f", "csv", "--count-as-pattern", `re:_spec\.rb$:Ruby Spec:Ruby`, "./examples/countaspattern/")650	if err != nil {651		t.Fatal(err)652	}653	if !strings.Contains(output, "Ruby Spec,") {654		t.Errorf("regex count-as-pattern failed, output:\n%s", output)655	}656}657658func TestCountAsPatternInvalidSkipped(t *testing.T) {659	// An unresolvable base language is reported and skipped, the run still works660	output, err := runSCC("-f", "csv", "--count-as-pattern", "glob:*_spec.rb:Ruby Spec:Nonexistent", "./examples/countaspattern/")661	if err != nil {662		t.Fatal(err)663	}664	if !strings.Contains(output, "is not a known language or extension") {665		t.Errorf("expected error message for unknown base language, output:\n%s", output)666	}667	if strings.Contains(output, "Ruby Spec,") {668		t.Errorf("Ruby Spec should not appear when the rule was skipped, output:\n%s", output)669	}670}671672func TestRemapUnknown(t *testing.T) {673	t.Parallel()674	output, err := runSCC("-f", "csv", "--remap-unknown", "-*- C++ -*-:C Header", "./examples/remap/unknown")675	if err != nil {676		t.Fatal(err)677	}678	if !strings.Contains(output, "C Header,") {679		t.Fatalf("remap unknown failed, output:\n%s", output)680	}681}682683func TestRemapAll(t *testing.T) {684	t.Parallel()685	output, err := runSCC("-f", "csv", "--remap-all", "-*- C++ -*-:C Header", "./examples/remap/java.java")686	if err != nil {687		t.Fatal(err)688	}689	if !strings.Contains(output, "C Header,") {690		t.Fatalf("remap all failed, output:\n%s", output)691	}692}693694func TestCocomoProjectType(t *testing.T) {695	projectTypes := []string{"organic", "semi-detached", "embedded", "custom,1,1,1,1"}696	for _, typ := range projectTypes {697		output, err := runSCC("--cocomo-project-type", typ)698		if err != nil {699			t.Fatal(err)700		}701		if !strings.Contains(output, fmt.Sprintf("Estimated Cost to Develop (%s)", typ)) ||702			!strings.Contains(output, fmt.Sprintf("Estimated Schedule Effort (%s)", typ)) ||703			!strings.Contains(output, fmt.Sprintf("Estimated People Required (%s)", typ)) {704			t.Errorf("check cocomo project type failed: %s", typ)705		}706	}707}708709func TestCocomoProjectTypeFallback(t *testing.T) {710	unknownTypes := []string{"doesnotexist", "custom,1,1,1"}711	for _, typ := range unknownTypes {712		output, err := runSCC("--cocomo-project-type", typ)713		if err != nil {714			t.Fatal(err)715		}716		if !strings.Contains(output, "Estimated Cost to Develop (organic)") ||717			!strings.Contains(output, "Estimated Schedule Effort (organic)") ||718			!strings.Contains(output, "Estimated People Required (organic)") {719			t.Errorf("check cocomo project type fallback failed: %s", typ)720		}721	}722}723724func TestOutputBytes(t *testing.T) {725	jsonOutput, err := runSCC("-f", "json")726	if err != nil {727		t.Fatal(err)728	}729	if !strings.Contains(jsonOutput, `"Bytes":`) {730		t.Errorf("json output does not contain `Bytes` field, output:\n%s", jsonOutput)731	}732733	output, err := runSCC()734	if err != nil {735		t.Fatal(err)736	}737	if !strings.Contains(output, "megabytes") {738		t.Errorf("output does not contain `megabytes`, output:\n%s", output)739	}740}741742func TestFileGCCount(t *testing.T) {743	const target = "./examples/duplicates"744	files, err := os.ReadDir(target)745	if err != nil {746		t.Fatal(err)747	}748749	output, err := runSCC("--file-gc-count", strconv.Itoa(len(files)-1), "-v", target)750	if err != nil {751		t.Fatal(err)752	}753	if !strings.Contains(output, "read file limit exceeded GC re-enabled") {754		t.Errorf("test file GC count failed, file count: %d, limit: %d", len(files), len(files)-1)755	}756757	output, err = runSCC("--file-gc-count", strconv.Itoa(len(files)+1), "-v", target)758	if err != nil {759		t.Fatal(err)760	}761	if strings.Contains(output, "read file limit exceeded GC re-enabled") {762		t.Errorf("test file GC count failed, file count: %d, limit: %d", len(files), len(files)+1)763	}764}765766func TestIncludeSymlinks(t *testing.T) {767	if runtime.GOOS == "windows" {768		t.Skip("skipping symlink test on Windows due to privilege requirements")769	}770771	tmpDir, err := filepath.EvalSymlinks(t.TempDir())772	if err != nil {773		t.Fatal(err)774	}775776	dirA := filepath.Join(tmpDir, "a")777	dirB := filepath.Join(tmpDir, "b")778	if err := os.Mkdir(dirA, 0755); err != nil {779		t.Fatal(err)780	}781	if err := os.Mkdir(dirB, 0755); err != nil {782		t.Fatal(err)783	}784785	const fileName = "source.go"786	if err := os.WriteFile(filepath.Join(dirA, fileName), []byte("package main\n"), 0644); err != nil {787		t.Fatal(err)788	}789	if err := os.WriteFile(filepath.Join(dirB, fileName), []byte("package main\n"), 0644); err != nil {790		t.Fatal(err)791	}792	// link to another dir, should be counted under --include-symlinks793	if err := os.Symlink(filepath.Join(dirB, fileName), filepath.Join(dirA, "link1.go")); err != nil {794		t.Fatal(err)795	}796	// link to the same dir, this should be ignored in all times797	if err := os.Symlink(filepath.Join(dirA, fileName), filepath.Join(dirA, "link2.go")); err != nil {798		t.Fatal(err)799	}800801	output, err := runSCC("-f", "json", "--no-scc-ignore", dirA)802	if err != nil {803		t.Fatal(err)804	}805	if !strings.Contains(output, `"Count":1`) {806		t.Errorf("count without symlink failed, output:\n%s", output)807	}808809	output, err = runSCC("-f", "json", "--no-scc-ignore", "--include-symlinks", dirA)810	if err != nil {811		t.Fatal(err)812	}813	if !strings.Contains(output, `"Count":2`) {814		t.Errorf("count includes symlink failed, output:\n%s", output)815	}816}817818func TestLanguageNameTruncate(t *testing.T) {819	output, err := runSCC("examples/language")820	if err != nil {821		t.Fatal(err)822	}823	if strings.Count(output, "Bitbucket Pipe…") != 1 {824		t.Errorf("`Bitbucket Pipeline` truncate test failed")825	}826	if strings.Count(output, "CloudFormation…") != 2 {827		t.Errorf("`CloudFormation (JSON)` and `CloudFormation (YAML)` truncate test failed")828	}829}830831func TestSpecificLanguages(t *testing.T) {832	languages := [...]string{833		"ABNF",834		"Alchemist",835		"Algol 68",836		"Alloy",837		"Amber",838		"Apex",839		"ArkTs",840		"Arturo",841		"Astro",842		"AWK",843		"BASH",844		"Bean",845		"Bicep",846		"Bitbucket Pipeline",847		"Blueprint",848		"Boo",849		"Bosque",850		"Bru",851		"C",852		"C3",853		"C Header",854		"C Shell",855		"C#",856		"C++",857		"C++ Header",858		"Cairo",859		"Cangjie",860		"Chapel",861		"Circom",862		"Clipper",863		"Clojure",864		"CMake",865		"Cuda",866		"Cypher",867		"D2",868		"DAML",869		"DM",870		"Docker ignore",871		"Dockerfile",872		"DOT",873		"Elixir Template",874		"Elm",875		"EmiT",876		"F#",877		"Factor",878		"Flow9",879		"FSL",880		"Futhark",881		"FXML",882		"Gemfile",883		"Gleam",884		"Go",885		"Go+",886		"Godot Scene",887		"GraphQL",888		"Gremlin",889		"Gwion",890		"HAML",891		"Hare",892		"Haskell",893		"HCL",894		"IEC61131-3",895		"ignore",896		"INI",897		"Java",898		"JavaScript",899		"JCL",900		"JSON5",901		"JSONC",902		"jq",903		"Korn Shell",904		"Koto",905		"LALRPOP",906		"License",907		"LiveScript",908		"LLVM IR",909		"Lua",910		"Luau",911		"Luna",912		"MLIR",913		"Makefile",914		"Metal",915		"Mojo",916		"Monkey C",917		"Moonbit",918		"Move",919		"Nature",920		"Nushell",921		"OpenQASM",922		"OpenTofu",923		"Patch",924		"Perl",925		"Pkl",926		"Plain Text",927		"POML",928		"PostScript",929		"Proto",930		"Python",931		"Q#",932		"R",933		"Racket",934		"Rakefile",935		"RAML",936		"Rebol",937		"Redscript",938		"Rich Text Format",939		"Scallop",940		"Seed7",941		"Shell",942		"Sieve",943		"Slang",944		"Slint",945		"Smalltalk",946		"Snakemake",947		"Stan",948		"Systemd",949		"Tact",950		"Teal",951		"Tera",952		"Templ",953		"Terraform",954		"TOML",955		"TOON",956		"TTCN-3",957		"TypeScript",958		"TypeSpec",959		"Typst",960		"Up",961		"Vala",962		"Vim Script",963		"Web Services Description Language",964		"WebGPU Enhanced Shading Language",965		"wenyan",966		"Wren",967		"XHTML",968		"XMake",969		"XML Schema",970		"YAML",971		"Yarn",972		"Zen C",973		"Zig",974		"ZoKrates",975		"Zsh",976	}977978	output, err := runSCC("-f", "csv", "examples/language")979	if err != nil {980		t.Fatal(err)981	}982983	for _, language := range languages {984		if !strings.Contains(output, language+",") {985			t.Errorf("language not found in output: %v", language)986		}987	}988}

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.