11package main
22
33import (
4+ "crypto/sha256"
5+ "encoding/hex"
46 "os"
57 "os/exec"
68 "path/filepath"
@@ -10,6 +12,7 @@ import (
1012 buildinfo "github.com/jfrog/build-info-go/entities"
1113 biutils "github.com/jfrog/build-info-go/utils"
1214 "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils"
15+ coreBuild "github.com/jfrog/jfrog-cli-core/v2/common/build"
1316 coretests "github.com/jfrog/jfrog-cli-core/v2/utils/tests"
1417 "github.com/jfrog/jfrog-cli/inttestutils"
1518 "github.com/jfrog/jfrog-cli/utils/tests"
@@ -469,6 +472,369 @@ func cleanupConanRemote() {
469472 _ = exec .Command ("conan" , "remote" , "remove" , tests .ConanLocalRepo ).Run ()
470473}
471474
475+ // TestConanCreateWithProjectKey tests that 'jf conan create --project=<key>' stores build info
476+ // in the correct local build dir (SHA includes the project key).
477+ func TestConanCreateWithProjectKey (t * testing.T ) {
478+ initConanTest (t )
479+ buildName := tests .ConanBuildName + "-project"
480+ buildNumber := "1"
481+ projectKey := "testprj"
482+
483+ projectPath := createConanProject (t , "conan-create-project-test" )
484+ wd , err := os .Getwd ()
485+ require .NoError (t , err )
486+ chdirCallback := clientTestUtils .ChangeDirWithCallback (t , wd , projectPath )
487+ defer chdirCallback ()
488+
489+ configureConanRemote (t )
490+ defer cleanupConanRemote ()
491+
492+ jfrogCli := coretests .NewJfrogCli (execMain , "jfrog" , "" )
493+ createArgs := []string {
494+ "conan" , "create" , "." ,
495+ "--build=missing" ,
496+ "--build-name=" + buildName ,
497+ "--build-number=" + buildNumber ,
498+ "--project=" + projectKey ,
499+ }
500+ require .NoError (t , jfrogCli .Exec (createArgs ... ))
501+
502+ // Verify local build info was stored under the project-key-aware directory
503+ builds , err := coreBuild .GetGeneratedBuildsInfo (buildName , buildNumber , projectKey )
504+ require .NoError (t , err )
505+ require .Len (t , builds , 1 , "Expected 1 build info file stored with project key '%s'" , projectKey )
506+ assert .Equal (t , buildName , builds [0 ].Name )
507+ assert .Equal (t , buildNumber , builds [0 ].Number )
508+
509+ // Verify that the build is NOT found under the empty-project directory
510+ buildsNoProject , err := coreBuild .GetGeneratedBuildsInfo (buildName , buildNumber , "" )
511+ assert .NoError (t , err )
512+ assert .Empty (t , buildsNoProject , "Build info should NOT exist under empty project key directory" )
513+
514+ // Cleanup local build dir
515+ assert .NoError (t , coreBuild .RemoveBuildDir (buildName , buildNumber , projectKey ))
516+ }
517+
518+ // TestConanCreateWithoutProjectKey verifies no regression: 'jf conan create' without --project
519+ // still stores build info in the default (empty project) directory.
520+ func TestConanCreateWithoutProjectKey (t * testing.T ) {
521+ initConanTest (t )
522+ buildName := tests .ConanBuildName + "-noproject"
523+ buildNumber := "1"
524+
525+ projectPath := createConanProject (t , "conan-create-noproject-test" )
526+ wd , err := os .Getwd ()
527+ require .NoError (t , err )
528+ chdirCallback := clientTestUtils .ChangeDirWithCallback (t , wd , projectPath )
529+ defer chdirCallback ()
530+
531+ configureConanRemote (t )
532+ defer cleanupConanRemote ()
533+
534+ jfrogCli := coretests .NewJfrogCli (execMain , "jfrog" , "" )
535+ createArgs := []string {
536+ "conan" , "create" , "." ,
537+ "--build=missing" ,
538+ "--build-name=" + buildName ,
539+ "--build-number=" + buildNumber ,
540+ }
541+ require .NoError (t , jfrogCli .Exec (createArgs ... ))
542+
543+ // Verify local build info was stored under the empty project directory
544+ builds , err := coreBuild .GetGeneratedBuildsInfo (buildName , buildNumber , "" )
545+ require .NoError (t , err )
546+ require .Len (t , builds , 1 , "Expected 1 build info file stored without project key" )
547+ assert .Equal (t , buildName , builds [0 ].Name )
548+
549+ // Cleanup local build dir
550+ assert .NoError (t , coreBuild .RemoveBuildDir (buildName , buildNumber , "" ))
551+ }
552+
553+ // TestConanInstallWithProjectKey tests that 'jf conan install --project=<key>' stores
554+ // build info in the project-key-aware directory.
555+ func TestConanInstallWithProjectKey (t * testing.T ) {
556+ initConanTest (t )
557+ buildName := tests .ConanBuildName + "-install-project"
558+ buildNumber := "1"
559+ projectKey := "testprj"
560+
561+ projectPath := createConanProject (t , "conan-install-project-test" )
562+ wd , err := os .Getwd ()
563+ require .NoError (t , err )
564+ chdirCallback := clientTestUtils .ChangeDirWithCallback (t , wd , projectPath )
565+ defer chdirCallback ()
566+
567+ configureConanRemote (t )
568+ defer cleanupConanRemote ()
569+
570+ jfrogCli := coretests .NewJfrogCli (execMain , "jfrog" , "" )
571+ installArgs := []string {
572+ "conan" , "install" , "." ,
573+ "--build=missing" ,
574+ "-r" , tests .ConanVirtualRepo ,
575+ "--build-name=" + buildName ,
576+ "--build-number=" + buildNumber ,
577+ "--project=" + projectKey ,
578+ }
579+ require .NoError (t , jfrogCli .Exec (installArgs ... ))
580+
581+ // Verify local build info was stored with project key
582+ builds , err := coreBuild .GetGeneratedBuildsInfo (buildName , buildNumber , projectKey )
583+ require .NoError (t , err )
584+ require .Len (t , builds , 1 , "Expected 1 build info file stored with project key '%s'" , projectKey )
585+
586+ // Verify NOT stored under empty-project dir
587+ buildsNoProject , err := coreBuild .GetGeneratedBuildsInfo (buildName , buildNumber , "" )
588+ assert .NoError (t , err )
589+ assert .Empty (t , buildsNoProject , "Build info should NOT exist under empty project key directory" )
590+
591+ // Cleanup
592+ assert .NoError (t , coreBuild .RemoveBuildDir (buildName , buildNumber , projectKey ))
593+ }
594+
595+ // TestConanUploadWithProjectKey tests that 'jf conan upload --project=<key>' stores
596+ // build info in the project-key-aware directory.
597+ func TestConanUploadWithProjectKey (t * testing.T ) {
598+ initConanTest (t )
599+ buildName := tests .ConanBuildName + "-upload-project"
600+ buildNumber := "1"
601+ projectKey := "testprj"
602+
603+ projectPath := createConanProject (t , "conan-upload-project-test" )
604+ wd , err := os .Getwd ()
605+ require .NoError (t , err )
606+ chdirCallback := clientTestUtils .ChangeDirWithCallback (t , wd , projectPath )
607+ defer chdirCallback ()
608+
609+ configureConanRemote (t )
610+ defer cleanupConanRemote ()
611+
612+ jfrogCli := coretests .NewJfrogCli (execMain , "jfrog" , "" )
613+
614+ // First create the package so it can be uploaded
615+ createArgs := []string {
616+ "conan" , "create" , "." ,
617+ "--build=missing" ,
618+ "--build-name=" + buildName ,
619+ "--build-number=" + buildNumber ,
620+ "--project=" + projectKey ,
621+ }
622+ require .NoError (t , jfrogCli .Exec (createArgs ... ))
623+
624+ // Upload with --project
625+ uploadArgs := []string {
626+ "conan" , "upload" , "cli-test-package/*" ,
627+ "-r" , tests .ConanLocalRepo ,
628+ "--confirm" ,
629+ "--build-name=" + buildName ,
630+ "--build-number=" + buildNumber ,
631+ "--project=" + projectKey ,
632+ }
633+ require .NoError (t , jfrogCli .Exec (uploadArgs ... ))
634+
635+ // Verify local build info was stored with project key
636+ builds , err := coreBuild .GetGeneratedBuildsInfo (buildName , buildNumber , projectKey )
637+ require .NoError (t , err )
638+ require .NotEmpty (t , builds , "Expected build info files stored with project key '%s'" , projectKey )
639+
640+ // Cleanup
641+ assert .NoError (t , coreBuild .RemoveBuildDir (buildName , buildNumber , projectKey ))
642+ }
643+
644+ // TestConanBuildPublishWithProjectKey verifies that build info stored with --project
645+ // is isolated from builds without --project.
646+ func TestConanBuildPublishWithProjectKey (t * testing.T ) {
647+ initConanTest (t )
648+ buildName := tests .ConanBuildName + "-bp-project"
649+ buildNumber := "1"
650+ projectKey := "testprj"
651+
652+ projectPath := createConanProject (t , "conan-bp-project-test" )
653+ wd , err := os .Getwd ()
654+ require .NoError (t , err )
655+ chdirCallback := clientTestUtils .ChangeDirWithCallback (t , wd , projectPath )
656+ defer chdirCallback ()
657+
658+ configureConanRemote (t )
659+ defer cleanupConanRemote ()
660+
661+ jfrogCli := coretests .NewJfrogCli (execMain , "jfrog" , "" )
662+
663+ // Create with --project so build info is stored with project key in the dir hash
664+ createArgs := []string {
665+ "conan" , "create" , "." ,
666+ "--build=missing" ,
667+ "--build-name=" + buildName ,
668+ "--build-number=" + buildNumber ,
669+ "--project=" + projectKey ,
670+ }
671+ require .NoError (t , jfrogCli .Exec (createArgs ... ))
672+
673+ // Verify the project-key-aware build dir has build info files
674+ buildsInfo , err := coreBuild .GetGeneratedBuildsInfo (buildName , buildNumber , projectKey )
675+ require .NoError (t , err )
676+ assert .NotEmpty (t , buildsInfo , "Build info should exist in the project-key-aware build dir" )
677+
678+ // Verify the build dir WITHOUT project key does NOT have build info files
679+ buildsInfoNoProject , err := coreBuild .GetGeneratedBuildsInfo (buildName , buildNumber , "" )
680+ require .NoError (t , err )
681+ assert .Empty (t , buildsInfoNoProject , "Build info should NOT exist in the non-project build dir" )
682+
683+ // Cleanup
684+ assert .NoError (t , coreBuild .RemoveBuildDir (buildName , buildNumber , projectKey ))
685+ assert .NoError (t , coreBuild .RemoveBuildDir (buildName , buildNumber , "" ))
686+ }
687+
688+ // TestConanProjectKeyBuildDirHash verifies the SHA256 directory name computation.
689+ // With --project=vsm the local build dir should be SHA256("buildName_buildNumber_vsm"),
690+ func TestConanProjectKeyBuildDirHash (t * testing.T ) {
691+ initConanTest (t )
692+ buildName := tests .ConanBuildName + "-hash"
693+ buildNumber := "1"
694+ projectKey := "vsm"
695+
696+ projectPath := createConanProject (t , "conan-hash-test" )
697+ wd , err := os .Getwd ()
698+ require .NoError (t , err )
699+ chdirCallback := clientTestUtils .ChangeDirWithCallback (t , wd , projectPath )
700+ defer chdirCallback ()
701+
702+ configureConanRemote (t )
703+ defer cleanupConanRemote ()
704+
705+ jfrogCli := coretests .NewJfrogCli (execMain , "jfrog" , "" )
706+ createArgs := []string {
707+ "conan" , "create" , "." ,
708+ "--build=missing" ,
709+ "--build-name=" + buildName ,
710+ "--build-number=" + buildNumber ,
711+ "--project=" + projectKey ,
712+ }
713+ require .NoError (t , jfrogCli .Exec (createArgs ... ))
714+
715+ // Compute expected directory names
716+ expectedWithProject := sha256 .Sum256 ([]byte (buildName + "_" + buildNumber + "_" + projectKey ))
717+ expectedDirWithProject := hex .EncodeToString (expectedWithProject [:])
718+ buggyNoProject := sha256 .Sum256 ([]byte (buildName + "_" + buildNumber + "_" ))
719+ buggyDirNoProject := hex .EncodeToString (buggyNoProject [:])
720+
721+ // Get the actual build dir path (should include project key)
722+ buildDir , err := coreBuild .GetBuildDir (buildName , buildNumber , projectKey )
723+ require .NoError (t , err )
724+ actualDirName := filepath .Base (buildDir )
725+
726+ assert .Equal (t , expectedDirWithProject , actualDirName ,
727+ "Build dir should be SHA256 of '%s_%s_%s'" , buildName , buildNumber , projectKey )
728+ assert .NotEqual (t , buggyDirNoProject , actualDirName ,
729+ "Build dir must NOT be SHA256 of '%s_%s_' (old bug)" , buildName , buildNumber )
730+
731+ // Verify build info file exists in that directory
732+ builds , err := coreBuild .GetGeneratedBuildsInfo (buildName , buildNumber , projectKey )
733+ require .NoError (t , err )
734+ require .Len (t , builds , 1 , "Expected build info to exist in the project-key-aware directory" )
735+
736+ assert .NoError (t , coreBuild .RemoveBuildDir (buildName , buildNumber , projectKey ))
737+ }
738+
739+ // TestConanMultipleProjectKeys tests that builds with different project keys
740+ // are stored in separate directories and don't interfere with each other.
741+ func TestConanMultipleProjectKeys (t * testing.T ) {
742+ initConanTest (t )
743+ buildName := tests .ConanBuildName + "-multiproj"
744+ buildNumber := "1"
745+ projectKeys := []string {"proj1" , "proj2" , "" }
746+
747+ projectPath := createConanProject (t , "conan-multi-project-test" )
748+ wd , err := os .Getwd ()
749+ require .NoError (t , err )
750+ chdirCallback := clientTestUtils .ChangeDirWithCallback (t , wd , projectPath )
751+ defer chdirCallback ()
752+
753+ configureConanRemote (t )
754+ defer cleanupConanRemote ()
755+
756+ jfrogCli := coretests .NewJfrogCli (execMain , "jfrog" , "" )
757+
758+ // Run create with different project keys
759+ for _ , pk := range projectKeys {
760+ args := []string {
761+ "conan" , "create" , "." ,
762+ "--build=missing" ,
763+ "--build-name=" + buildName ,
764+ "--build-number=" + buildNumber ,
765+ }
766+ if pk != "" {
767+ args = append (args , "--project=" + pk )
768+ }
769+ require .NoError (t , jfrogCli .Exec (args ... ), "Failed for project key '%s'" , pk )
770+ }
771+
772+ // Verify each project key has its own build info, isolated from the others
773+ for _ , pk := range projectKeys {
774+ builds , err := coreBuild .GetGeneratedBuildsInfo (buildName , buildNumber , pk )
775+ require .NoError (t , err )
776+ require .Len (t , builds , 1 , "Expected exactly 1 build info for project key '%s'" , pk )
777+ assert .Equal (t , buildName , builds [0 ].Name )
778+ }
779+
780+ // Verify all build dirs are distinct (different SHA)
781+ dirs := make (map [string ]string )
782+ for _ , pk := range projectKeys {
783+ buildDir , err := coreBuild .GetBuildDir (buildName , buildNumber , pk )
784+ require .NoError (t , err )
785+ dirName := filepath .Base (buildDir )
786+ existingPk , exists := dirs [dirName ]
787+ assert .False (t , exists , "Project keys '%s' and '%s' produced the same build dir %s" , existingPk , pk , dirName )
788+ dirs [dirName ] = pk
789+ }
790+
791+ // Cleanup all
792+ for _ , pk := range projectKeys {
793+ assert .NoError (t , coreBuild .RemoveBuildDir (buildName , buildNumber , pk ))
794+ }
795+ }
796+
797+ // TestConanProjectKeySpaceSeparated tests that --project <value>
798+ // works the same as --project=<value>.
799+ func TestConanProjectKeySpaceSeparated (t * testing.T ) {
800+ initConanTest (t )
801+ buildName := tests .ConanBuildName + "-space-project"
802+ buildNumber := "1"
803+ projectKey := "testprj"
804+
805+ projectPath := createConanProject (t , "conan-space-project-test" )
806+ wd , err := os .Getwd ()
807+ require .NoError (t , err )
808+ chdirCallback := clientTestUtils .ChangeDirWithCallback (t , wd , projectPath )
809+ defer chdirCallback ()
810+
811+ configureConanRemote (t )
812+ defer cleanupConanRemote ()
813+
814+ jfrogCli := coretests .NewJfrogCli (execMain , "jfrog" , "" )
815+ // Note: --project testprj (space-separated, not --project=testprj)
816+ createArgs := []string {
817+ "conan" , "create" , "." ,
818+ "--build=missing" ,
819+ "--build-name=" + buildName ,
820+ "--build-number=" + buildNumber ,
821+ "--project" , projectKey ,
822+ }
823+ require .NoError (t , jfrogCli .Exec (createArgs ... ))
824+
825+ // Verify local build info was stored with project key
826+ builds , err := coreBuild .GetGeneratedBuildsInfo (buildName , buildNumber , projectKey )
827+ require .NoError (t , err )
828+ require .Len (t , builds , 1 , "Expected 1 build info file stored with space-separated --project" )
829+
830+ // Verify NOT stored under empty-project dir
831+ buildsNoProject , err := coreBuild .GetGeneratedBuildsInfo (buildName , buildNumber , "" )
832+ assert .NoError (t , err )
833+ assert .Empty (t , buildsNoProject , "Build info should NOT exist under empty project key directory" )
834+
835+ assert .NoError (t , coreBuild .RemoveBuildDir (buildName , buildNumber , projectKey ))
836+ }
837+
472838// TestConanBuildPublishWithCIVcsProps tests that CI VCS properties are set on Conan artifacts
473839// when running build-publish in a CI environment (GitHub Actions).
474840// Conan packages are published via Conan client; build-publish retrieves artifact paths
0 commit comments