Goroutine without waitgroup or channel; risks resource leaks or race conditions
go func() {
1// SPDX-License-Identifier: MIT23package processor45import (6 "io"7 "os"8 "slices"9 "strings"10 "testing"1112 "github.com/mattn/go-runewidth"13)1415func TestCalculateCocomo(t *testing.T) {16 var str strings.Builder17 calculateCocomo(1, &str)1819 if !strings.Contains(str.String(), "Estimated Schedule Effort (organic) 0.22 months") {20 t.Error("expected to match got", str.String())21 }22}2324func TestCalculateSizeSingleByte(t *testing.T) {25 var str strings.Builder26 calculateSize(1, &str)2728 if !strings.Contains(str.String(), "Processed 1 bytes, 0.000 megabytes (SI)") {29 t.Error("expected to match got", str.String())30 }31}3233func TestCalculateSize(t *testing.T) {34 var str strings.Builder35 calculateSize(1000000, &str)3637 if !strings.Contains(str.String(), "Processed 1000000 bytes, 1.000 megabytes (SI)") {38 t.Error("expected to match got", str.String())39 }40}4142func TestSortSummaryFilesEmpty(t *testing.T) {43 summary := LanguageSummary{}44 sortSummaryFiles(&summary)45}4647func TestSortSummaryFiles(t *testing.T) {48 files := []*FileJob{}49 files = append(files, &FileJob{50 Language: "Go",51 Filename: "bbbb.go",52 Extension: "go",53 Location: "./bbbb.go",54 Bytes: 1000,55 Lines: 1000,56 Code: 1000,57 Comment: 1000,58 Blank: 1000,59 Complexity: 1000,60 WeightedComplexity: 1000,61 Binary: false,62 })63 files = append(files, &FileJob{64 Language: "Go",65 Filename: "aaaa.go",66 Extension: "go",67 Location: "./aaaa.go",68 Bytes: 2000,69 Lines: 2000,70 Code: 2000,71 Comment: 2000,72 Blank: 2000,73 Complexity: 2000,74 WeightedComplexity: 2000,75 Binary: false,76 })7778 summary := LanguageSummary{79 Name: "Go",80 Bytes: 1000,81 Lines: 1000,82 Code: 1000,83 Comment: 1000,84 Blank: 1000,85 Complexity: 1000,86 Count: 1000,87 WeightedComplexity: 1000,88 Files: files,89 }9091 lineSort := []string{"name", "names", "language", "languages", "line", "lines", "RANDOMTHING"}92 for _, val := range lineSort {93 SortBy = val94 sortSummaryFiles(&summary)9596 if summary.Files[0].Filename != "aaaa.go" {97 t.Error("Sorting on lines failed", val)98 }99 }100101 blankSort := []string{"blank", "blanks"}102 for _, val := range blankSort {103 SortBy = val104 sortSummaryFiles(&summary)105106 if summary.Files[0].Filename != "aaaa.go" {107 t.Error("Sorting on blank failed", val)108 }109 }110111 codeSort := []string{"code", "codes"}112 for _, val := range codeSort {113 SortBy = val114 sortSummaryFiles(&summary)115116 if summary.Files[0].Filename != "aaaa.go" {117 t.Error("Sorting on code failed", val)118 }119 }120121 commentSort := []string{"comment", "comments"}122 for _, val := range commentSort {123 SortBy = val124 sortSummaryFiles(&summary)125126 if summary.Files[0].Filename != "aaaa.go" {127 t.Error("Sorting on comment failed", val)128 }129 }130131 complexitySort := []string{"complexity", "complexitys"}132 for _, val := range complexitySort {133 SortBy = val134 sortSummaryFiles(&summary)135136 if summary.Files[0].Filename != "aaaa.go" {137 t.Error("Sorting on complexity failed", val)138 }139 }140}141142func TestSortSummaryFilesName(t *testing.T) {143 goFiles := []*FileJob{}144 goFiles = append(goFiles, &FileJob{145 Language: "Go",146 Location: "bbbb.go",147 })148149 goFiles = append(goFiles, &FileJob{150 Language: "Go",151 Location: "aaaa.go",152 })153154 goFiles = append(goFiles, &FileJob{155 Language: "Go",156 Location: "cccc.go",157 })158159 summary := LanguageSummary{160 Name: "Go",161 Files: goFiles,162 }163164 lineSort := []string{"name", "names", "language", "languages"}165 for _, val := range lineSort {166 SortBy = val167 sortSummaryFiles(&summary)168169 if summary.Files[0].Location != "aaaa.go" {170 t.Error("Sorting on lines failed", val)171 }172 }173 SortBy = ""174}175176func TestSortLanguageSummaryName(t *testing.T) {177 SortBy = "name"178 ls := []LanguageSummary{179 {180 Name: "b",181 Lines: 1,182 },183 {184 Name: "a",185 Lines: 1,186 },187 }188189 ls = sortLanguageSummary(ls)190191 if ls[0].Name != "a" {192 t.Error("Expected a to be first")193 }194}195196func TestSortLanguageSummaryLine(t *testing.T) {197 SortBy = "line"198 ls := []LanguageSummary{199 {200 Name: "a",201 Lines: 1,202 },203 {204 Name: "b",205 Lines: 1,206 },207 {208 Name: "c",209 Lines: 2,210 },211 }212213 ls = sortLanguageSummary(ls)214215 if ls[0].Name != "c" || ls[1].Name != "a" {216 t.Error("Expected c to be first and a second")217 }218}219220func TestSortLanguageSummaryBlank(t *testing.T) {221 SortBy = "blank"222 ls := []LanguageSummary{223 {224 Name: "a",225 Blank: 1,226 },227 {228 Name: "b",229 Blank: 1,230 },231 {232 Name: "c",233 Blank: 2,234 },235 }236237 ls = sortLanguageSummary(ls)238239 if ls[0].Name != "c" || ls[1].Name != "a" {240 t.Error("Expected c to be first and a second")241 }242}243244func TestSortLanguageSummaryCode(t *testing.T) {245 SortBy = "code"246 ls := []LanguageSummary{247 {248 Name: "a",249 Code: 1,250 },251 {252 Name: "b",253 Code: 1,254 },255 {256 Name: "c",257 Code: 2,258 },259 }260261 ls = sortLanguageSummary(ls)262263 if ls[0].Name != "c" || ls[1].Name != "a" {264 t.Error("Expected c to be first and a second")265 }266}267268func TestSortLanguageSummaryComment(t *testing.T) {269 SortBy = "comment"270 ls := []LanguageSummary{271 {272 Name: "a",273 Comment: 1,274 },275 {276 Name: "b",277 Comment: 1,278 },279 {280 Name: "c",281 Comment: 2,282 },283 }284285 ls = sortLanguageSummary(ls)286287 if ls[0].Name != "c" || ls[1].Name != "a" {288 t.Error("Expected c to be first and a second")289 }290}291292func TestSortLanguageSummaryComplexity(t *testing.T) {293 SortBy = "complexity"294 ls := []LanguageSummary{295 {296 Name: "a",297 Complexity: 1,298 },299 {300 Name: "b",301 Complexity: 1,302 },303 {304 Name: "c",305 Complexity: 2,306 },307 }308309 ls = sortLanguageSummary(ls)310311 if ls[0].Name != "c" || ls[1].Name != "a" {312 t.Error("Expected c to be first and a second")313 }314}315316func TestSortLanguageSummaryBytes(t *testing.T) {317 SortBy = "bytes"318 ls := []LanguageSummary{319 {320 Name: "a",321 Bytes: 1,322 },323 {324 Name: "b",325 Bytes: 1,326 },327 {328 Name: "c",329 Bytes: 2,330 },331 }332333 ls = sortLanguageSummary(ls)334335 if ls[0].Name != "c" || ls[1].Name != "a" {336 t.Error("Expected c to be first and a second")337 }338}339340func TestSortLanguageSummaryFiles(t *testing.T) {341 SortBy = "files"342 ls := []LanguageSummary{343 {344 Name: "a",345 Count: 1,346 },347 {348 Name: "b",349 Count: 1,350 },351 {352 Name: "c",353 Count: 2,354 },355 }356357 ls = sortLanguageSummary(ls)358359 if ls[0].Name != "c" || ls[1].Name != "a" {360 t.Error("Expected c to be first and a second")361 }362}363364func TestSortSummaryNames(t *testing.T) {365 SortBy = "name"366 ls := []LanguageSummary{367 {368 Name: "a",369 Complexity: 1,370 },371 {372 Name: "b",373 Complexity: 1,374 },375 {376 Name: "c",377 Complexity: 2,378 },379 }380381 ls = sortLanguageSummary(ls)382383 if ls[0].Name != "a" || ls[1].Name != "b" || ls[2].Name != "c" {384 t.Error("Expected a to be first and b second and c third")385 }386}387388func TestToJSONEmpty(t *testing.T) {389 inputChan := make(chan *FileJob, 1000)390 close(inputChan)391 res := toJSON(inputChan)392393 if res != "[]" {394 t.Error("Expected empty JSON return", res)395 }396}397398func TestToJSONSingle(t *testing.T) {399 inputChan := make(chan *FileJob, 1000)400 inputChan <- &FileJob{401 Language: "Go",402 Filename: "bbbb.go",403 Extension: "go",404 Location: "./",405 Bytes: 1000,406 Lines: 1000,407 Code: 1000,408 Comment: 1000,409 Blank: 1000,410 Complexity: 1000,411 WeightedComplexity: 1000,412 Binary: false,413 }414 close(inputChan)415 Debug = true // Increase coverage slightly416 Files = true417 res := toJSON(inputChan)418 Debug = false419420 if !strings.Contains(res, `"Name":"Go"`) || !strings.Contains(res, `"Code":1000`) || !strings.Contains(res, `"Filename":"bbbb.go"`) {421 t.Error("Expected JSON return", res)422 }423 if strings.Contains(res, `"Content":`) {424 t.Error("Expected JSON return", res)425 }426}427428func TestToJSONSingleWithoutFiles(t *testing.T) {429 inputChan := make(chan *FileJob, 1000)430 inputChan <- &FileJob{431 Language: "Go",432 Filename: "bbbb.go",433 Extension: "go",434 Location: "./",435 Bytes: 1000,436 Lines: 1000,437 Code: 1000,438 Comment: 1000,439 Blank: 1000,440 Complexity: 1000,441 WeightedComplexity: 1000,442 Binary: false,443 }444 close(inputChan)445 Debug = true // Increase coverage slightly446 Files = false447 res := toJSON(inputChan)448 Debug = false449450 if !strings.Contains(res, `"Name":"Go"`) || !strings.Contains(res, `"Code":1000`) {451 t.Error("Expected JSON return", res)452 }453 if strings.Contains(res, `"Filename":"bbbb.go"`) {454 t.Error("Expected JSON return", res)455 }456}457458func TestToJSONMultiple(t *testing.T) {459 inputChan := make(chan *FileJob, 1000)460 inputChan <- &FileJob{461 Language: "Go",462 Filename: "bbbb.go",463 Extension: "go",464 Location: "./",465 Bytes: 1000,466 Lines: 1000,467 Code: 1000,468 Comment: 1000,469 Blank: 1000,470 Complexity: 1000,471 WeightedComplexity: 1000,472 Binary: false,473 }474 inputChan <- &FileJob{475 Language: "Go",476 Filename: "aaaa.go",477 Extension: "go",478 Location: "./",479 Bytes: 1000,480 Lines: 1000,481 Code: 1000,482 Comment: 1000,483 Blank: 1000,484 Complexity: 1000,485 WeightedComplexity: 1000,486 Binary: false,487 }488 close(inputChan)489 Debug = true // Increase coverage slightly490 Files = true491 res := toJSON(inputChan)492 Debug = false493494 if !strings.Contains(res, `aaaa.go`) || !strings.Contains(res, `bbbb.go`) {495 t.Error("Expected JSON return", res)496 }497}498499func TestToYAMLEmpty(t *testing.T) {500 inputChan := make(chan *FileJob, 1000)501 close(inputChan)502 res := toClocYAML(inputChan)503504 if !strings.Contains(res, "{}") || !strings.Contains(res, "header:") || !strings.Contains(res, "n_files: 0") {505 t.Error("Expected empty Cloc YAML return", res)506 }507}508509func TestToYAMLSingle(t *testing.T) {510 inputChan := make(chan *FileJob, 1000)511 inputChan <- &FileJob{512 Language: "Go",513 Filename: "bbbb.go",514 Extension: "go",515 Location: "./",516 Bytes: 1000,517 Lines: 1000,518 Code: 1000,519 Comment: 1000,520 Blank: 1000,521 Complexity: 1000,522 WeightedComplexity: 1000,523 Binary: false,524 }525 close(inputChan)526 Debug = true // Increase coverage slightly527 res := toClocYAML(inputChan)528 Debug = false529530 if !strings.Contains(res, `n_lines: 1000`) {531 t.Error("Expected Cloc YAML return", res)532 }533}534535func TestToYAMLMultiple(t *testing.T) {536 inputChan := make(chan *FileJob, 1000)537 inputChan <- &FileJob{538 Language: "Go",539 Filename: "bbbb.go",540 Extension: "go",541 Location: "./",542 Bytes: 1000,543 Lines: 1000,544 Code: 1000,545 Comment: 1000,546 Blank: 1000,547 Complexity: 1000,548 WeightedComplexity: 1000,549 Binary: false,550 }551 inputChan <- &FileJob{552 Language: "Go",553 Filename: "aaaa.go",554 Extension: "go",555 Location: "./",556 Bytes: 1000,557 Lines: 1000,558 Code: 1000,559 Comment: 1000,560 Blank: 1000,561 Complexity: 1000,562 WeightedComplexity: 1000,563 Binary: false,564 }565 close(inputChan)566 Debug = true // Increase coverage slightly567 res := toClocYAML(inputChan)568 Debug = false569570 if !strings.Contains(res, `code: 2000`) || !strings.Contains(res, `n_lines: 2000`) {571 t.Error("Expected Cloc JSON return", res)572 }573}574575func TestToCsvMultiple(t *testing.T) {576 inputChan := make(chan *FileJob, 1000)577 inputChan <- &FileJob{578 Language: "Go",579 Filename: "bbbb.go",580 Extension: "go",581 Location: "./",582 Bytes: 1000,583 Lines: 1000,584 Code: 1000,585 Comment: 1000,586 Blank: 1000,587 Complexity: 1000,588 WeightedComplexity: 1000,589 Binary: false,590 }591 inputChan <- &FileJob{592 Language: "Go",593 Filename: "aaaa.go",594 Extension: "go",595 Location: "./",596 Bytes: 1000,597 Lines: 1000,598 Code: 1000,599 Comment: 1000,600 Blank: 1000,601 Complexity: 1000,602 WeightedComplexity: 1000,603 Binary: false,604 }605 close(inputChan)606 Debug = true // Increase coverage slightly607 res := toCSV(inputChan)608 Debug = false609610 if !strings.Contains(res, `aaaa.go,`) || !strings.Contains(res, `bbbb.go`) {611 t.Error("Expected CSV return", res)612 }613}614615func TestToCsvStreamMultiple(t *testing.T) {616 inputChan := make(chan *FileJob, 1000)617 inputChan <- &FileJob{618 Language: "Go",619 Filename: "bbbb.go",620 Extension: "go",621 Location: "./",622 Bytes: 1000,623 Lines: 1000,624 Code: 1000,625 Comment: 1000,626 Blank: 1000,627 Complexity: 1000,628 WeightedComplexity: 1000,629 Binary: false,630 }631 inputChan <- &FileJob{632 Language: "Go",633 Filename: "aaaa.go",634 Extension: "go",635 Location: "./",636 Bytes: 1000,637 Lines: 1000,638 Code: 1000,639 Comment: 1000,640 Blank: 1000,641 Complexity: 1000,642 WeightedComplexity: 1000,643 Binary: false,644 }645 close(inputChan)646 Debug = true // Increase coverage slightly647 res := toCSVStream(inputChan)648 Debug = false649650 if res != "" {651 t.Error("Expected CSV return", res)652 }653}654655func TestToCsvFilesSorted(t *testing.T) {656 fj1 := &FileJob{657 Language: "Go",658 Filename: "bbbb.go",659 Extension: "go",660 Location: "./",661 Bytes: 90,662 Lines: 90,663 Code: 90,664 Comment: 90,665 Blank: 90,666 Complexity: 90,667 WeightedComplexity: 90,668 Binary: false,669 }670 fj2 := &FileJob{671 Language: "Go",672 Filename: "aaaa.go",673 Extension: "go",674 Location: "./",675 Bytes: 1000,676 Lines: 1000,677 Code: 1000,678 Comment: 1000,679 Blank: 1000,680 Complexity: 1000,681 WeightedComplexity: 1000,682 Binary: false,683 }684685 Files = true686 SortBy = "lines"687688 inputChan1 := make(chan *FileJob, 1000)689 inputChan1 <- fj1690 inputChan1 <- fj2691 close(inputChan1)692 res1 := toCSV(inputChan1)693694 inputChan2 := make(chan *FileJob, 1000)695 inputChan2 <- fj2696 inputChan2 <- fj1697 close(inputChan2)698 res2 := toCSV(inputChan2)699700 Files = false701702 if res1 != res2 {703 t.Error("Should be sorted to be the same")704 }705}706707func TestToOpenMetricsMultiple(t *testing.T) {708 inputChan := make(chan *FileJob, 1000)709 inputChan <- &FileJob{710 Language: "Go",711 Filename: "bbbb.go",712 Extension: "go",713 Location: "./",714 Bytes: 1000,715 Lines: 1000,716 Code: 1000,717 Comment: 1000,718 Blank: 1000,719 Complexity: 1000,720 WeightedComplexity: 1000,721 Binary: false,722 }723 inputChan <- &FileJob{724 Language: "Go",725 Filename: "aaaa.go",726 Extension: "go",727 Location: "./",728 Bytes: 1000,729 Lines: 1000,730 Code: 1000,731 Comment: 1000,732 Blank: 1000,733 Complexity: 1000,734 WeightedComplexity: 1000,735 Binary: false,736 }737 close(inputChan)738 Files = false739 Debug = true // Increase coverage slightly740 res := toOpenMetrics(inputChan)741 Debug = false742743 var expectedResult = `# TYPE scc_files gauge744# HELP scc_files Number of sourcecode files.745# TYPE scc_lines gauge746# HELP scc_lines Number of lines.747# TYPE scc_code gauge748# HELP scc_code Number of lines of actual code.749# TYPE scc_comments gauge750# HELP scc_comments Number of comments.751# TYPE scc_blanks gauge752# HELP scc_blanks Number of blank lines.753# TYPE scc_complexity gauge754# HELP scc_complexity Code complexity.755# TYPE scc_bytes gauge756# UNIT scc_bytes bytes757# HELP scc_bytes Size in bytes.758scc_files{language="Go"} 2759scc_lines{language="Go"} 2000760scc_code{language="Go"} 2000761scc_comments{language="Go"} 2000762scc_blanks{language="Go"} 2000763scc_complexity{language="Go"} 2000764scc_bytes{language="Go"} 2000765`766767 if res != expectedResult {768 t.Error("Expected OpenMetrics return", res)769 }770}771772func TestToSQLSingle(t *testing.T) {773 inputChan := make(chan *FileJob, 1000)774 inputChan <- &FileJob{775 Language: "Go",776 Filename: "bbbb.go",777 Extension: "go",778 Location: "./",779 Bytes: 1000,780 Lines: 1000,781 Code: 1000,782 Comment: 1000,783 Blank: 1000,784 Complexity: 1000,785 WeightedComplexity: 1000,786 Binary: false,787 Uloc: 99,788 }789 close(inputChan)790 Files = false791 Debug = true // Increase coverage slightly792 res := toSql(inputChan)793 Debug = false794795 if !strings.Contains(res, `create table metadata`) {796 t.Error("Expected create table return", res)797 }798799 if !strings.Contains(res, `create table t`) {800 t.Error("Expected create table return", res)801 }802803 if !strings.Contains(res, `begin transaction`) {804 t.Error("Expected begin transaction return", res)805 }806807 if !strings.Contains(res, `insert into t values('', 'Go', './', './', 'bbbb.go', 1000, 1000, 1000, 1000, 1000, 99);`) {808 t.Error("Expected insert return", res)809 }810811 if !strings.Contains(res, `insert into metadata values`) {812 t.Error("Expected insert return", res)813 }814}815816func TestFileSummarizeWide(t *testing.T) {817 inputChan := make(chan *FileJob, 1000)818 inputChan <- &FileJob{819 Language: "Go",820 Filename: "bbbb.go",821 Extension: "go",822 Location: "./",823 Bytes: 1000,824 Lines: 1000,825 Code: 1000,826 Comment: 1000,827 Blank: 1000,828 Complexity: 1000,829 WeightedComplexity: 1000,830 Binary: false,831 }832833 close(inputChan)834 Format = "wide"835 More = true836 res := fileSummarize(inputChan)837 More = false838839 if !strings.Contains(res, `Language`) {840 t.Error("Expected CSV return", res)841 }842}843844func TestFileSummarizeJson(t *testing.T) {845 inputChan := make(chan *FileJob, 1000)846 inputChan <- &FileJob{847 Language: "Go",848 Filename: "bbbb.go",849 Extension: "go",850 Location: "./",851 Bytes: 1000,852 Lines: 1000,853 Code: 1000,854 Comment: 1000,855 Blank: 1000,856 Complexity: 1000,857 WeightedComplexity: 1000,858 Binary: false,859 }860861 close(inputChan)862 Format = "JSON"863 More = false864 Files = true865 res := fileSummarize(inputChan)866867 if !strings.Contains(res, `bbbb.go`) || !strings.HasPrefix(res, "[") {868 t.Error("Expected JSON return", res)869 }870}871872func TestFileSummarizeCsv(t *testing.T) {873 inputChan := make(chan *FileJob, 1000)874 inputChan <- &FileJob{875 Language: "Go",876 Filename: "bbbb.go",877 Extension: "go",878 Location: "./",879 Bytes: 1000,880 Lines: 1000,881 Code: 1000,882 Comment: 1000,883 Blank: 1000,884 Complexity: 1000,885 WeightedComplexity: 1000,886 Binary: false,887 }888889 close(inputChan)890 Format = "CSV"891 More = false892 res := fileSummarize(inputChan)893894 if !strings.Contains(res, `bbbb.go`) {895 t.Error("Expected CSV return", res)896 }897}898899func TestFileSummarizeYaml(t *testing.T) {900 inputChan := make(chan *FileJob, 1000)901 inputChan <- &FileJob{902 Language: "Go",903 Filename: "bbbb.go",904 Extension: "go",905 Location: "./",906 Bytes: 1000,907 Lines: 1000,908 Code: 1000,909 Comment: 1000,910 Blank: 1000,911 Complexity: 1000,912 WeightedComplexity: 1000,913 Binary: false,914 }915916 close(inputChan)917 Format = "cloc-yml"918 More = false919 res := fileSummarize(inputChan)920921 if !strings.Contains(res, `code: 1000`) {922 t.Error("Expected YAML return", res)923 }924}925926func TestFileSummarizeYml(t *testing.T) {927 inputChan := make(chan *FileJob, 1000)928 inputChan <- &FileJob{929 Language: "Go",930 Filename: "bbbb.go",931 Extension: "go",932 Location: "./",933 Bytes: 1000,934 Lines: 1000,935 Code: 1000,936 Comment: 1000,937 Blank: 1000,938 Complexity: 1000,939 WeightedComplexity: 1000,940 Binary: false,941 }942943 close(inputChan)944 Format = "cloc-YAML"945 More = false946 res := fileSummarize(inputChan)947948 if !strings.Contains(res, `code: 1000`) {949 t.Error("Expected YML return", res)950 }951}952953func TestFileSummarizeOpenMetrics(t *testing.T) {954 inputChan := make(chan *FileJob, 1000)955 inputChan <- &FileJob{956 Language: "Go",957 Filename: "bbbb.go",958 Extension: "go",959 Location: "./",960 Bytes: 1000,961 Lines: 1000,962 Code: 1000,963 Comment: 1000,964 Blank: 1000,965 Complexity: 1000,966 WeightedComplexity: 1000,967 Binary: false,968 }969970 close(inputChan)971 Files = false972 Format = "OpenMetrics"973 More = false974 res := fileSummarize(inputChan)975976 var expectedResult = `# TYPE scc_files gauge977# HELP scc_files Number of sourcecode files.978# TYPE scc_lines gauge979# HELP scc_lines Number of lines.980# TYPE scc_code gauge981# HELP scc_code Number of lines of actual code.982# TYPE scc_comments gauge983# HELP scc_comments Number of comments.984# TYPE scc_blanks gauge985# HELP scc_blanks Number of blank lines.986# TYPE scc_complexity gauge987# HELP scc_complexity Code complexity.988# TYPE scc_bytes gauge989# UNIT scc_bytes bytes990# HELP scc_bytes Size in bytes.991scc_files{language="Go"} 1992scc_lines{language="Go"} 1000993scc_code{language="Go"} 1000994scc_comments{language="Go"} 1000995scc_blanks{language="Go"} 1000996scc_complexity{language="Go"} 1000997scc_bytes{language="Go"} 1000998`9991000 if res != expectedResult {1001 t.Error("Expected OpenMetrics return", res)1002 }1003}10041005func TestFileSummarizeOpenMetricsPerFile(t *testing.T) {1006 inputChan := make(chan *FileJob, 1000)1007 inputChan <- &FileJob{1008 Language: "Go",1009 Filename: "bbbb.go",1010 Extension: "go",1011 Location: "C:\\bbbb.go", // to test escaping of the backslash1012 Bytes: 1000,1013 Lines: 1000,1014 Code: 1000,1015 Comment: 1000,1016 Blank: 1000,1017 Complexity: 1000,1018 WeightedComplexity: 1000,1019 Binary: false,1020 }10211022 close(inputChan)1023 Format = "OpenMetrics"1024 More = false1025 Files = true1026 res := fileSummarize(inputChan)10271028 var expectedResult = `# TYPE scc_files gauge1029# HELP scc_files Number of sourcecode files.1030# TYPE scc_lines gauge1031# HELP scc_lines Number of lines.1032# TYPE scc_code gauge1033# HELP scc_code Number of lines of actual code.1034# TYPE scc_comments gauge1035# HELP scc_comments Number of comments.1036# TYPE scc_blanks gauge1037# HELP scc_blanks Number of blank lines.1038# TYPE scc_complexity gauge1039# HELP scc_complexity Code complexity.1040# TYPE scc_bytes gauge1041# UNIT scc_bytes bytes1042# HELP scc_bytes Size in bytes.1043scc_lines{language="Go",file="C:\\bbbb.go"} 10001044scc_code{language="Go",file="C:\\bbbb.go"} 10001045scc_comments{language="Go",file="C:\\bbbb.go"} 10001046scc_blanks{language="Go",file="C:\\bbbb.go"} 10001047scc_complexity{language="Go",file="C:\\bbbb.go"} 10001048scc_bytes{language="Go",file="C:\\bbbb.go"} 10001049# EOF1050`10511052 if res != expectedResult {1053 t.Error("Expected OpenMetrics return", res)1054 }1055}10561057func TestFileSummarizeHtml(t *testing.T) {1058 inputChan := make(chan *FileJob, 1000)1059 inputChan <- &FileJob{1060 Language: "Go",1061 Filename: "bbbb.go",1062 Extension: "go",1063 Location: "./",1064 Bytes: 1000,1065 Lines: 1000,1066 Code: 1000,1067 Comment: 1000,1068 Blank: 1000,1069 Complexity: 1000,1070 WeightedComplexity: 1000,1071 Binary: false,1072 }10731074 close(inputChan)1075 Format = "html"1076 More = false1077 res := fileSummarize(inputChan)10781079 if !strings.Contains(res, `<th>1000`) {1080 t.Error("Expected HTML return", res)1081 }1082}10831084func TestFileSummarizeHtmlTable(t *testing.T) {1085 inputChan := make(chan *FileJob, 1000)1086 inputChan <- &FileJob{1087 Language: "Go",1088 Filename: "bbbb.go",1089 Extension: "go",1090 Location: "./",1091 Bytes: 1000,1092 Lines: 1000,1093 Code: 1000,1094 Comment: 1000,1095 Blank: 1000,1096 Complexity: 1000,1097 WeightedComplexity: 1000,1098 Binary: false,1099 }11001101 close(inputChan)1102 Format = "html-table"1103 More = false1104 res := fileSummarize(inputChan)11051106 if !strings.Contains(res, `<th>1000`) {1107 t.Error("Expected HTML-table return", res)1108 }1109}11101111func TestFileSummarizeDefault(t *testing.T) {1112 inputChan := make(chan *FileJob, 1000)1113 inputChan <- &FileJob{1114 Language: "Go",1115 Filename: "bbbb.go",1116 Extension: "go",1117 Location: "./",1118 Bytes: 1000,1119 Lines: 1000,1120 Code: 1000,1121 Comment: 1000,1122 Blank: 1000,1123 Complexity: 1000,1124 WeightedComplexity: 1000,1125 Binary: false,1126 }11271128 close(inputChan)1129 Format = ""1130 More = false1131 res := fileSummarize(inputChan)11321133 if !strings.Contains(res, `Estimated Cost to Develop`) {1134 t.Error("Expected summary return", res)1135 }1136}11371138func TestFileSummarizeLong(t *testing.T) {1139 inputChan := make(chan *FileJob, 1000)1140 inputChan <- &FileJob{1141 Language: "Go",1142 Filename: "bbbb.go",1143 Extension: "go",1144 Location: "./",1145 Bytes: 1000,1146 Lines: 1000,1147 Code: 1000,1148 Comment: 1000,1149 Blank: 1000,1150 Complexity: 1000,1151 WeightedComplexity: 1000,1152 Binary: false,1153 }1154 inputChan <- &FileJob{1155 Language: "Go",1156 Filename: "aaaa.go",1157 Extension: "go",1158 Location: "./",1159 Bytes: 1000,1160 Lines: 1000,1161 Code: 1000,1162 Comment: 1000,1163 Blank: 1000,1164 Complexity: 1000,1165 WeightedComplexity: 1000,1166 Binary: false,1167 }1168 close(inputChan)1169 res := fileSummarizeLong(inputChan)11701171 if !strings.Contains(res, `Language`) {1172 t.Error("Expected Summary return", res)1173 }1174}11751176func TestFileSummarizeShort(t *testing.T) {1177 inputChan := make(chan *FileJob, 1000)1178 inputChan <- &FileJob{1179 Language: "Go",1180 Filename: "bbbb.go",1181 Extension: "go",1182 Location: "./",1183 Bytes: 1000,1184 Lines: 1000,1185 Code: 1000,1186 Comment: 1000,1187 Blank: 1000,1188 Complexity: 1000,1189 WeightedComplexity: 1000,1190 Binary: false,1191 }1192 inputChan <- &FileJob{1193 Language: "Go",1194 Filename: "aaaa.go",1195 Extension: "go",1196 Location: "./",1197 Bytes: 1000,1198 Lines: 1000,1199 Code: 1000,1200 Comment: 1000,1201 Blank: 1000,1202 Complexity: 1000,1203 WeightedComplexity: 1000,1204 Binary: false,1205 }1206 close(inputChan)1207 res := fileSummarizeShort(inputChan)12081209 if !strings.Contains(res, `Language`) {1210 t.Error("Expected Summary return", res)1211 }1212}12131214func TestFileSummarizeShortSort(t *testing.T) {1215 inputChan := make(chan *FileJob, 1000)1216 inputChan <- &FileJob{1217 Language: "Go",1218 Filename: "bbbb.go",1219 Extension: "go",1220 Location: "./",1221 Bytes: 1000,1222 Lines: 1000,1223 Code: 1000,1224 Comment: 1000,1225 Blank: 1000,1226 Complexity: 1000,1227 WeightedComplexity: 1000,1228 Binary: false,1229 }1230 inputChan <- &FileJob{1231 Language: "Go",1232 Filename: "bbbb.go",1233 Extension: "go",1234 Location: "./",1235 Bytes: 1000,1236 Lines: 1000,1237 Code: 1000,1238 Comment: 1000,1239 Blank: 1000,1240 Complexity: 1000,1241 WeightedComplexity: 1000,1242 Binary: false,1243 }1244 close(inputChan)12451246 sortBy := []string{"name", "line", "blank", "code", "comment"}12471248 Files = true1249 for _, sort := range sortBy {1250 SortBy = sort1251 res := fileSummarizeShort(inputChan)12521253 if !strings.Contains(res, `Language`) {1254 t.Error("Expected Summary return", res)1255 }1256 }1257}12581259func TestFileSummarizeLongSort(t *testing.T) {1260 inputChan := make(chan *FileJob, 1000)1261 inputChan <- &FileJob{1262 Language: "Go",1263 Filename: "bbbb.go",1264 Extension: "go",1265 Location: "./",1266 Bytes: 1000,1267 Lines: 1000,1268 Code: 1000,1269 Comment: 1000,1270 Blank: 1000,1271 Complexity: 1000,1272 WeightedComplexity: 1000,1273 Binary: false,1274 }1275 inputChan <- &FileJob{1276 Language: "Go",1277 Filename: "bbbb.go",1278 Extension: "go",1279 Location: "./",1280 Bytes: 1000,1281 Lines: 1000,1282 Code: 1000,1283 Comment: 1000,1284 Blank: 1000,1285 Complexity: 1000,1286 WeightedComplexity: 1000,1287 Binary: false,1288 }1289 close(inputChan)12901291 sortBy := []string{"name", "line", "blank", "code", "comment"}12921293 Files = true1294 for _, sort := range sortBy {1295 SortBy = sort1296 res := fileSummarizeLong(inputChan)12971298 if !strings.Contains(res, `Language`) {1299 t.Error("Expected Summary return", res)1300 }1301 }1302}13031304func TestGetTabularShortBreak(t *testing.T) {1305 Ci = false1306 r := getTabularShortBreak()13071308 if !strings.Contains(r, "─") {1309 t.Errorf("Expected to have box line")1310 }13111312 Ci = true1313 r = getTabularShortBreak()13141315 if !strings.Contains(r, "-") {1316 t.Errorf("Expected to have hyphen")1317 }13181319 Ci = false1320}13211322func TestGetTabularWideBreak(t *testing.T) {1323 {1324 Ci, HBorder = false, false1325 r := getTabularWideBreak()1326 if !strings.Contains(r, "─") {1327 t.Errorf("Expected to have box line")1328 }1329 }1330 {1331 Ci, HBorder = false, true1332 r := getTabularWideBreak()1333 if strings.Contains(r, "─") {1334 t.Errorf("Didn't expect to have box line")1335 }1336 }1337 {1338 Ci, HBorder = true, false1339 r := getTabularWideBreak()1340 if !strings.Contains(r, "-") {1341 t.Errorf("Expected to have hyphen")1342 }1343 }1344 {1345 Ci, HBorder = true, true1346 r := getTabularWideBreak()1347 if strings.Contains(r, "-") {1348 t.Errorf("Didn't expect to have hyphen")1349 }1350 }13511352 Ci, HBorder = false, false1353}13541355func TestToHTML(t *testing.T) {1356 inputChan := make(chan *FileJob, 1000)1357 inputChan <- &FileJob{1358 Language: "Go",1359 Filename: "bbbb.go",1360 Extension: "go",1361 Location: "./",1362 Bytes: 1000,1363 Lines: 1000,1364 Code: 1000,1365 Comment: 1000,1366 Blank: 1000,1367 Complexity: 1000,1368 WeightedComplexity: 1000,1369 Binary: false,1370 }1371 close(inputChan)1372 res := toHtml(inputChan)13731374 if !strings.Contains(res, `<html lang="en">`) {1375 t.Error("Expected to have HTML wrapper")1376 }13771378 if !strings.Contains(res, "<th>Language</th>") {1379 t.Error("html Language check failed")1380 }1381 if !strings.Contains(res, "<th>Files</th>") {1382 t.Error("html Files check failed")1383 }1384 if !strings.Contains(res, "<th>Lines</th>") {1385 t.Error("html Lines check failed")1386 }1387 if !strings.Contains(res, "<th>Blank</th>") {1388 t.Error("html Blank check failed")1389 }1390 if !strings.Contains(res, "<th>Comment</th>") {1391 t.Error("html Comment check failed")1392 }1393 if !strings.Contains(res, "<th>Code</th>") {1394 t.Error("html Code check failed")1395 }1396 if !strings.Contains(res, "<th>Complexity</th>") {1397 t.Error("html Complexity check failed")1398 }1399 if !strings.Contains(res, "<th>Bytes</th>") {1400 t.Error("html Bytes check failed")1401 }1402 if !strings.Contains(res, "<th>Uloc</th>") {1403 t.Error("html Uloc check failed")1404 }1405}14061407func TestToHTMLTable(t *testing.T) {1408 inputChan := make(chan *FileJob, 1000)1409 inputChan <- &FileJob{1410 Language: "Go",1411 Filename: "bbbb.go",1412 Extension: "go",1413 Location: "./",1414 Bytes: 1000,1415 Lines: 1000,1416 Code: 1000,1417 Comment: 1000,1418 Blank: 1000,1419 Complexity: 1000,1420 WeightedComplexity: 1000,1421 Binary: false,1422 }1423 close(inputChan)1424 res := toHtmlTable(inputChan)14251426 if strings.Contains(res, `<html lang="en">`) {1427 t.Error("Expected to not have wrapper")1428 }14291430 if !strings.Contains(res, `<table id="scc-table">`) {1431 t.Error("Expected to have table element")1432 }1433}14341435func TestUnicodeAwareTrimAscii(t *testing.T) {1436 tmp := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.md"1437 res := unicodeAwareTrim(tmp, shortFormatFileTruncate)1438 if res != "~aaaaaaaaaaaaaaaaaaaaaaa.md" {1439 t.Error("expected ~aaaaaaaaaaaaaaaaaaaaaaa.md got", res)1440 }1441}14421443func TestUnicodeAwareTrimExactSizeAscii(t *testing.T) {1444 tmp := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.md"1445 res := unicodeAwareTrim(tmp, len(tmp))1446 if res != tmp {1447 t.Errorf("expected %s got %s", tmp, res)1448 }1449}14501451func TestUnicodeAwareTrimUnicode(t *testing.T) {1452 tmp := "中文中文中文中文中文中文中文中文中文中文中文中文中文中文中文中文.md"1453 res := unicodeAwareTrim(tmp, shortFormatFileTruncate)1454 if res != "~文中文中文中文中文中文.md" {1455 t.Error("expected ~文中文中文中文中文中文.md got", res)1456 }1457}14581459func TestUnicodeAwareRightPad(t *testing.T) {1460 tmp := unicodeAwareRightPad("", 10)1461 if runewidth.StringWidth(tmp) != 10 {1462 t.Errorf("expected length of 10")1463 }1464}14651466func TestUnicodeAwareRightPadUnicode(t *testing.T) {1467 tmp := unicodeAwareRightPad("中文", 10)1468 if runewidth.StringWidth(tmp) != 10 {1469 t.Errorf("expected length of 10")1470 }1471}14721473func BenchmarkUnicodeAwareTrimExactSizeAscii(b *testing.B) {1474 tmp := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.md"1475 for b.Loop() {1476 res := unicodeAwareTrim(tmp, len(tmp))1477 if res != tmp {1478 b.Fatalf("expected %s got %s", tmp, res)1479 }1480 }1481}14821483func BenchmarkUnicodeAwareTrimUnicode(b *testing.B) {1484 tmp := "中文中文中文中文中文中文中文中文中文中文中文中文中文中文中文中文.md"1485 for b.Loop() {1486 res := unicodeAwareTrim(tmp, shortFormatFileTruncate)1487 if res != "~文中文中文中文中文中文.md" {1488 b.Fatalf("expected ~文中文中文中文中文中文.md got %s", res)1489 }1490 }1491}14921493func BenchmarkUnicodeAwareRightPad(b *testing.B) {1494 for b.Loop() {1495 tmp := unicodeAwareRightPad("", 10)1496 if runewidth.StringWidth(tmp) != 10 {1497 b.Fatal("expected length of 10")1498 }1499 }1500}15011502func BenchmarkUnicodeAwareRightPadUnicode(b *testing.B) {1503 for b.Loop() {1504 tmp := unicodeAwareRightPad("中文", 10)1505 if runewidth.StringWidth(tmp) != 10 {1506 b.Fatal("expected length of 10")1507 }1508 }1509}15101511// When using columise ~28726 ns/op1512// When using optimised ~14293 ns/op1513func BenchmarkFileSummerize(b *testing.B) {1514 for i := 0; i < b.N; i++ {1515 b.StopTimer()1516 fileSummaryJobQueue := make(chan *FileJob, 1000)15171518 fileSummaryJobQueue <- &FileJob{1519 Blank: 1,1520 Bytes: 1,1521 Code: 1,1522 Comment: 1,1523 Complexity: 1,1524 Language: "Go",1525 Lines: 10,1526 }1527 fileSummaryJobQueue <- &FileJob{1528 Blank: 2,1529 Bytes: 2,1530 Code: 2,1531 Comment: 2,1532 Complexity: 2,1533 Language: "Python",1534 Lines: 20,1535 }1536 close(fileSummaryJobQueue)1537 b.StartTimer()15381539 fileSummarize(fileSummaryJobQueue)1540 }1541}15421543func TestGetCSVFilesSortFunc(t *testing.T) {1544 records := [][]string{1545 // Language,Provider,Filename,Lines,Code,Comments,Blanks,Complexity,Bytes,ULOC1546 {"Go", "/path/to/file", "go.go", "10", "10", "0", "1", "1", "1024", "0"},1547 {"Python", "/path/to/file", "python.py", "20", "20", "1", "2", "2", "2048", "0"},1548 {"C#", "/path/to/file", "csharp.cs", "30", "30", "2", "3", "3", "4096", "0"},1549 {"C++", "/path/to/file", "cpp.cpp", "40", "40", "3", "4", "4", "8192", "0"},1550 }1551 testCases := []struct {1552 sortBy string1553 expected []string1554 }{1555 {1556 sortBy: "names",1557 expected: []string{"C++", "C#", "Go", "Python"},1558 },1559 {1560 sortBy: "langs",1561 expected: []string{"C#", "C++", "Go", "Python"},1562 },1563 {1564 sortBy: "lines",1565 expected: []string{"C++", "C#", "Python", "Go"},1566 },1567 {1568 sortBy: "code",1569 expected: []string{"C++", "C#", "Python", "Go"},1570 },1571 {1572 sortBy: "comments",1573 expected: []string{"C++", "C#", "Python", "Go"},1574 },1575 {1576 sortBy: "blanks",1577 expected: []string{"C++", "C#", "Python", "Go"},1578 },1579 {1580 sortBy: "complexity",1581 expected: []string{"C++", "C#", "Python", "Go"},1582 },1583 {1584 sortBy: "bytes",1585 expected: []string{"C++", "C#", "Python", "Go"},1586 },1587 {1588 sortBy: "default",1589 expected: []string{"C++", "C#", "Go", "Python"},1590 },1591 }1592 for _, tc := range testCases {1593 data := slices.Clone(records) // always use an unordered records1594 slices.SortFunc(data, getCSVFilesSortFunc(tc.sortBy))1595 sortedRecords := make([]string, 0, len(data))1596 for i := range data {1597 sortedRecords = append(sortedRecords, data[i][0])1598 }1599 if !slices.Equal(sortedRecords, tc.expected) {1600 t.Errorf("sortBy: %s failed, expected: %v, got: %v", tc.sortBy, tc.expected, sortedRecords)1601 }1602 }1603}16041605func TestToCSVFilesHeader(t *testing.T) {1606 inputChan := make(chan *FileJob, 1000)1607 inputChan <- &FileJob{1608 Language: "Go",1609 Filename: "bbbb.go",1610 Extension: "go",1611 Location: "./",1612 Bytes: 1000,1613 Lines: 1000,1614 Code: 1000,1615 Comment: 1000,1616 Blank: 1000,1617 Complexity: 1000,1618 WeightedComplexity: 1000,1619 Binary: false,1620 }1621 inputChan <- &FileJob{1622 Language: "Go",1623 Filename: "aaaa.go",1624 Extension: "go",1625 Location: "./",1626 Bytes: 1000,1627 Lines: 1000,1628 Code: 1000,1629 Comment: 1000,1630 Blank: 1000,1631 Complexity: 1000,1632 WeightedComplexity: 1000,1633 Binary: false,1634 }1635 close(inputChan)1636 res := toCSVFiles(inputChan)1637 header, _, _ := strings.Cut(res, "\n")1638 const expected = "Language,Provider,Filename,Lines,Code,Comments,Blanks,Complexity,Bytes,ULOC"1639 if header != expected {1640 t.Errorf("check toCSVFiles header failed, expected: %v, got: %v", expected, header)1641 }1642}16431644func TestToCSVStreamHeader(t *testing.T) {1645 inputChan := make(chan *FileJob, 1000)1646 inputChan <- &FileJob{1647 Language: "Go",1648 Filename: "bbbb.go",1649 Extension: "go",1650 Location: "./",1651 Bytes: 1000,1652 Lines: 1000,1653 Code: 1000,1654 Comment: 1000,1655 Blank: 1000,1656 Complexity: 1000,1657 WeightedComplexity: 1000,1658 Binary: false,1659 }1660 inputChan <- &FileJob{1661 Language: "Go",1662 Filename: "aaaa.go",1663 Extension: "go",1664 Location: "./",1665 Bytes: 1000,1666 Lines: 1000,1667 Code: 1000,1668 Comment: 1000,1669 Blank: 1000,1670 Complexity: 1000,1671 WeightedComplexity: 1000,1672 Binary: false,1673 }1674 close(inputChan)16751676 originStdout := os.Stdout1677 t.Cleanup(func() {1678 os.Stdout = originStdout1679 })1680 r, w, err := os.Pipe()1681 if err != nil {1682 t.Fatal(err)1683 }1684 os.Stdout = w1685 go func() {1686 toCSVStream(inputChan)1687 _ = w.Close()1688 }()1689 output, err := io.ReadAll(r)1690 if err != nil {1691 t.Fatal(err)1692 }16931694 header, _, _ := strings.Cut(string(output), "\n")1695 const expected = "Language,Provider,Filename,Lines,Code,Comments,Blanks,Complexity,Bytes,Uloc"1696 if header != expected {1697 t.Errorf("check toCSVStream header failed, expected: %v, got: %v", expected, header)1698 }1699}17001701func TestToJSONKeys(t *testing.T) {1702 inputChan := make(chan *FileJob, 1000)1703 inputChan <- &FileJob{1704 Language: "Go",1705 Filename: "bbbb.go",1706 Extension: "go",1707 Location: "./",1708 Bytes: 1000,1709 Lines: 1000,1710 Code: 1000,1711 Comment: 1000,1712 Blank: 1000,1713 Complexity: 1000,1714 WeightedComplexity: 1000,1715 Binary: false,1716 }1717 inputChan <- &FileJob{1718 Language: "Go",1719 Filename: "aaaa.go",1720 Extension: "go",1721 Location: "./",1722 Bytes: 1000,1723 Lines: 1000,1724 Code: 1000,1725 Comment: 1000,1726 Blank: 1000,1727 Complexity: 1000,1728 WeightedComplexity: 1000,1729 Binary: false,1730 }1731 close(inputChan)17321733 res := toJSON(inputChan)1734 if !strings.Contains(res, `"Name":`) {1735 t.Error("JSON Name check failed")1736 }1737 if !strings.Contains(res, `"Files":`) {1738 t.Error("JSON Files check failed")1739 }1740 if !strings.Contains(res, `"Lines":`) {1741 t.Error("JSON Lines check failed")1742 }1743 if !strings.Contains(res, `"Blank":`) {1744 t.Error("JSON Blank check failed")1745 }1746 if !strings.Contains(res, `"Comment":`) {1747 t.Error("JSON Comment check failed")1748 }1749 if !strings.Contains(res, `"Code":`) {1750 t.Error("JSON Code check failed")1751 }1752 if !strings.Contains(res, `"Complexity":`) {1753 t.Error("JSON Complexity check failed")1754 }1755 if !strings.Contains(res, `"Bytes":`) {1756 t.Error("JSON Bytes check failed")1757 }1758 if !strings.Contains(res, `"ULOC":`) {1759 t.Error("JSON Uloc check failed")1760 }1761}17621763func TestToJSON2Keys(t *testing.T) {1764 inputChan := make(chan *FileJob, 1000)1765 inputChan <- &FileJob{1766 Language: "Go",1767 Filename: "bbbb.go",1768 Extension: "go",1769 Location: "./",1770 Bytes: 1000,1771 Lines: 1000,1772 Code: 1000,1773 Comment: 1000,1774 Blank: 1000,1775 Complexity: 1000,1776 WeightedComplexity: 1000,1777 Binary: false,1778 }1779 inputChan <- &FileJob{1780 Language: "Go",1781 Filename: "aaaa.go",1782 Extension: "go",1783 Location: "./",1784 Bytes: 1000,1785 Lines: 1000,1786 Code: 1000,1787 Comment: 1000,1788 Blank: 1000,1789 Complexity: 1000,1790 WeightedComplexity: 1000,1791 Binary: false,1792 }1793 close(inputChan)17941795 res := toJSON2(inputChan)1796 if !strings.Contains(res, `"Name":`) {1797 t.Error("JSON2 Name check failed")1798 }1799 if !strings.Contains(res, `"Files":`) {1800 t.Error("JSON2 Files check failed")1801 }1802 if !strings.Contains(res, `"Lines":`) {1803 t.Error("JSON2 Lines check failed")1804 }1805 if !strings.Contains(res, `"Blank":`) {1806 t.Error("JSON2 Blank check failed")1807 }1808 if !strings.Contains(res, `"Comment":`) {1809 t.Error("JSON2 Comment check failed")1810 }1811 if !strings.Contains(res, `"Code":`) {1812 t.Error("JSON2 Code check failed")1813 }1814 if !strings.Contains(res, `"Complexity":`) {1815 t.Error("JSON2 Complexity check failed")1816 }1817 if !strings.Contains(res, `"Bytes":`) {1818 t.Error("JSON2 Bytes check failed")1819 }1820 if !strings.Contains(res, `"ULOC":`) {1821 t.Error("JSON2 Uloc check failed")1822 }1823 if !strings.Contains(res, `"languageSummary":`) {1824 t.Error("JSON2 languageSummary check failed")1825 }1826 if !strings.Contains(res, `"estimatedCost":`) {1827 t.Error("JSON2 estimatedCost check failed")1828 }1829 if !strings.Contains(res, `"estimatedScheduleMonths":`) {1830 t.Error("JSON2 estimatedScheduleMonths check failed")1831 }1832 if !strings.Contains(res, `"estimatedPeople":`) {1833 t.Error("JSON2 estimatedPeople check failed")1834 }1835}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.