@@ -374,6 +374,131 @@ func TestPythonProject(t *testing.T) {
374374 }
375375}
376376
377+ func writeProjectFile (t * testing.T , dir , path , content string ) {
378+ t .Helper ()
379+ full := filepath .Join (dir , path )
380+ if err := os .MkdirAll (filepath .Dir (full ), 0o755 ); err != nil {
381+ t .Fatal (err )
382+ }
383+ if err := os .WriteFile (full , []byte (content ), 0o644 ); err != nil {
384+ t .Fatal (err )
385+ }
386+ }
387+
388+ func runOn (t * testing.T , dir string ) * brief.Report {
389+ t .Helper ()
390+ r , err := New (loadKB (t ), dir ).Run ()
391+ if err != nil {
392+ t .Fatalf ("Run: %v" , err )
393+ }
394+ return r
395+ }
396+
397+ func packageManagerNames (r * brief.Report ) []string {
398+ names := make ([]string , 0 , len (r .PackageManagers ))
399+ for _ , pm := range r .PackageManagers {
400+ names = append (names , pm .Name )
401+ }
402+ return names
403+ }
404+
405+ func TestPythonPackageManagerManifests (t * testing.T ) {
406+ cases := []struct {
407+ name string
408+ file string
409+ content string
410+ want string
411+ }{
412+ {"setup.py" , "setup.py" , "from setuptools import setup\n setup()\n " , "setuptools" },
413+ {"setup.cfg" , "setup.cfg" , "[metadata]\n name = x\n " , "setuptools" },
414+ {"pyproject setuptools" , "pyproject.toml" , "[build-system]\n requires = [\" setuptools\" ]\n " , "setuptools" },
415+ {"pyproject hatchling" , "pyproject.toml" , "[build-system]\n build-backend = \" hatchling.build\" \n " , "Hatch" },
416+ {"pyproject flit" , "pyproject.toml" , "[build-system]\n requires = [\" flit_core\" ]\n " , "Flit" },
417+ {"Pipfile" , "Pipfile" , "[packages]\n " , "Pipenv" },
418+ {"requirements.txt" , "requirements.txt" , "requests\n " , "pip" },
419+ }
420+ for _ , tc := range cases {
421+ t .Run (tc .name , func (t * testing.T ) {
422+ dir := t .TempDir ()
423+ writeProjectFile (t , dir , "main.py" , "" )
424+ writeProjectFile (t , dir , tc .file , tc .content )
425+ r := runOn (t , dir )
426+ got := packageManagerNames (r )
427+ found := false
428+ for _ , n := range got {
429+ if n == tc .want {
430+ found = true
431+ }
432+ }
433+ if ! found {
434+ t .Errorf ("manifest %s: want %q in package managers, got %v" , tc .file , tc .want , got )
435+ }
436+ })
437+ }
438+ }
439+
440+ func TestPythonFlatLayout (t * testing.T ) {
441+ dir := t .TempDir ()
442+ writeProjectFile (t , dir , "setup.py" , "" )
443+ writeProjectFile (t , dir , "mypkg/__init__.py" , "" )
444+ writeProjectFile (t , dir , "mypkg/core.py" , "" )
445+ writeProjectFile (t , dir , "tests/test_core.py" , "" )
446+ writeProjectFile (t , dir , "docs/index.md" , "" )
447+ writeProjectFile (t , dir , "scripts/release.py" , "" )
448+
449+ r := runOn (t , dir )
450+ if r .Layout == nil {
451+ t .Fatal ("expected layout info" )
452+ }
453+ if got := r .Layout .SourceDirs ; len (got ) != 1 || got [0 ] != "mypkg" {
454+ t .Errorf ("source_dirs = %v, want [mypkg]" , got )
455+ }
456+ for _ , d := range r .Layout .SourceDirs {
457+ if d == "docs" || d == "scripts" || d == "tests" {
458+ t .Errorf ("source_dirs should not include %q" , d )
459+ }
460+ }
461+ foundTests := false
462+ for _ , d := range r .Layout .TestDirs {
463+ if d == "tests" {
464+ foundTests = true
465+ }
466+ }
467+ if ! foundTests {
468+ t .Error ("expected tests/ in test_dirs" )
469+ }
470+ }
471+
472+ func TestFlatLayoutSkippedWhenSrcExists (t * testing.T ) {
473+ dir := t .TempDir ()
474+ writeProjectFile (t , dir , "src/mypkg/__init__.py" , "" )
475+ writeProjectFile (t , dir , "helper/tool.py" , "" )
476+
477+ r := runOn (t , dir )
478+ if r .Layout == nil {
479+ t .Fatal ("expected layout info" )
480+ }
481+ if got := r .Layout .SourceDirs ; len (got ) != 1 || got [0 ] != "src" {
482+ t .Errorf ("source_dirs = %v, want [src]" , got )
483+ }
484+ }
485+
486+ func TestFlatLayoutNonPython (t * testing.T ) {
487+ dir := t .TempDir ()
488+ writeProjectFile (t , dir , "Gemfile" , "source 'https://rubygems.org'\n " )
489+ writeProjectFile (t , dir , "mygem/version.rb" , "VERSION = '1.0'\n " )
490+ writeProjectFile (t , dir , "examples/demo.rb" , "" )
491+ writeProjectFile (t , dir , "spec/mygem_spec.rb" , "" )
492+
493+ r := runOn (t , dir )
494+ if r .Layout == nil {
495+ t .Fatal ("expected layout info" )
496+ }
497+ if got := r .Layout .SourceDirs ; len (got ) != 1 || got [0 ] != "mygem" {
498+ t .Errorf ("source_dirs = %v, want [mygem]" , got )
499+ }
500+ }
501+
377502func TestEmptyProject (t * testing.T ) {
378503 engine := New (loadKB (t ), "../testdata/empty-project" )
379504 r , err := engine .Run ()
0 commit comments