From ba1f510eba6d2eed08bd68bf373eb3ce57f00546 Mon Sep 17 00:00:00 2001 From: Alejandro R Mosteo Date: Mon, 27 Mar 2023 22:21:15 +0200 Subject: [PATCH] Test the local release with `alr test` (#1356) * Ability to test local crate * Test for local crate testing * self-review * Add user changes entry * Add check of release profile to tests --- doc/user-changes.md | 11 + src/alr/alr-commands-test.adb | 228 +++++++++++++------ src/alr/alr-commands-test.ads | 4 +- src/alr/alr-commands.adb | 13 ++ src/alr/alr-commands.ads | 3 + testsuite/drivers/asserts.py | 19 +- testsuite/tests/test/default-test/test.py | 6 +- testsuite/tests/test/local-release/test.py | 46 ++++ testsuite/tests/test/local-release/test.yaml | 1 + 9 files changed, 262 insertions(+), 69 deletions(-) create mode 100644 testsuite/tests/test/local-release/test.py create mode 100644 testsuite/tests/test/local-release/test.yaml diff --git a/doc/user-changes.md b/doc/user-changes.md index 97143e395..2fd5ec365 100644 --- a/doc/user-changes.md +++ b/doc/user-changes.md @@ -6,6 +6,17 @@ stay on top of `alr` new features. ## Release `1.3-dev` +### Test of a working crate with `alr test` + +PR [#1356](https://github.com/alire-project/alire/pull/1356) + +This PR enables the use of `alr test` on local crates. Previously, it could only +be used on indexed ones. + +By default, running `alr test` will build the crate in release mode. This +behavior can be overridden by defining one or more [test +actions](https://alire.ada.dev/docs/#release-information). + ### Binary releases moved to system cache from system config directory PR [#1349](https://github.com/alire-project/alire/pull/1349) diff --git a/src/alr/alr-commands-test.adb b/src/alr/alr-commands-test.adb index af71bfc47..99f3c0453 100644 --- a/src/alr/alr-commands-test.adb +++ b/src/alr/alr-commands-test.adb @@ -7,6 +7,7 @@ with Alire.Crates; with Alire.Defaults; with Alire.Dependencies; with Alire.Directories; +with Alire.Errors; with Alire.Index; with Alire.Milestones; with Alire.Origins; @@ -44,15 +45,19 @@ package body Alr.Commands.Test is -- Check_Files -- ----------------- - function Check_Files (Output : in out AAA.Strings.Vector; - R : Alire.Index.Release) return Boolean + function Check_Files (Cmd : in out Command; + Output : in out AAA.Strings.Vector; + R : Alire.Index.Release; + Local : Boolean) return Boolean is use AAA.Strings; use Ada.Directories; begin -- Declared GPR files in include paths declare - Guard : Folder_Guard (Enter_Folder (R.Base_Folder)) + Guard : Folder_Guard (Enter_Folder (if Local + then Cmd.Root.Path + else R.Base_Folder)) with Unreferenced; begin @@ -82,7 +87,7 @@ package body Alr.Commands.Test is -- Generated executables for Exe of R.Executables (Platform.Properties) loop - if Files.Locate_File_Under (Folder => R.Base_Folder, + if Files.Locate_File_Under (Folder => Alire.Directories.Current, Name => Exe, Max_Depth => Natural'Last).Is_Empty then @@ -101,8 +106,9 @@ package body Alr.Commands.Test is ------------- procedure Do_Test - (Cmd : Command; + (Cmd : in out Command; Releases : Alire.Releases.Containers.Release_Sets.Set; + Local : Boolean; Docker_Image : String) is use Ada.Calendar; @@ -123,6 +129,9 @@ package body Alr.Commands.Test is (Long_Long_Integer'Image (Long_Long_Integer (Clock - Time_Of (1970, 1, 1)))); + Test_Name : constant String + := "alr_test_" & (if Local then "local" else Timestamp); + Newline : constant String := "" & ASCII.LF; ------------------ @@ -133,6 +142,12 @@ package body Alr.Commands.Test is Output : AAA.Strings.Vector; Start : Time; + -- When testing the local crate, we must ensure being at the root + CD : Folder_Guard (if Local + then Enter_Folder (Cmd.Root.Path) + else Alire.Directories.Stay) + with Unreferenced; + ----------------- -- Test_Action -- ----------------- @@ -182,6 +197,8 @@ package body Alr.Commands.Test is ------------------ procedure Default_Test is + + -- Used to test indexed crates Alr_Args : constant AAA.Strings.Vector := Empty_Vector & Regular_Alr_Switches & @@ -191,12 +208,24 @@ package body Alr.Commands.Test is else To_Vector ("--build")) & R.Milestone.Image; + -- Used to test the local crate + Alr_Local : constant AAA.Strings.Vector := + Empty_Vector & + "alr" & + Regular_Alr_Switches & + "build" & + "--release"; + + -- Used to run inside docker Docker_Default : constant AAA.Strings.Vector := Docker_Prefix & Custom_Alr & Alr_Args; - Alr_Default : constant AAA.Strings.Vector := "alr" & Alr_Args; + Alr_Default : constant AAA.Strings.Vector + := (if Local + then Alr_Local + else "alr" & Alr_Args); Exit_Code : Integer; begin @@ -222,7 +251,7 @@ package body Alr.Commands.Test is -- Check declared gpr/executables in place if not R.Origin.Is_System and then - not Check_Files (Output, R) + not Cmd.Check_Files (Output, R, Local) then raise Child_Failed with "Declared executable(s) missing"; end if; @@ -245,33 +274,40 @@ package body Alr.Commands.Test is & R.Milestone.Image; begin - -- Fetch the crate - - if Alire.Utils.Command_Line_Contains (Docker_Switch) then - Output.Append_Line ("Spawning: " & Dkr_Custom_Cmd.Flatten); - Exit_Code := Unchecked_Spawn_And_Capture - (Dkr_Custom_Cmd.First_Element, - Dkr_Custom_Cmd.Tail, - Output, - Err_To_Out => True); - else - Output.Append_Line ("Spawning: " & Alr_Custom_Cmd.Flatten); - Exit_Code := Unchecked_Spawn_And_Capture - (Alr_Custom_Cmd.First_Element, - Alr_Custom_Cmd.Tail, - Output, - Err_To_Out => True); - end if; + -- Fetch the crate if not local test + + if not Local then + if Alire.Utils.Command_Line_Contains (Docker_Switch) then + Output.Append_Line + ("Spawning: " & Dkr_Custom_Cmd.Flatten); + Exit_Code := Unchecked_Spawn_And_Capture + (Dkr_Custom_Cmd.First_Element, + Dkr_Custom_Cmd.Tail, + Output, + Err_To_Out => True); + else + Output.Append_Line + ("Spawning: " & Alr_Custom_Cmd.Flatten); + Exit_Code := Unchecked_Spawn_And_Capture + (Alr_Custom_Cmd.First_Element, + Alr_Custom_Cmd.Tail, + Output, + Err_To_Out => True); + end if; - if Exit_Code /= 0 then - raise Child_Failed; + if Exit_Code /= 0 then + raise Child_Failed; + end if; end if; -- And run its actions in its working directory declare Guard : Alire.Directories.Guard - (Alire.Directories.Enter (R.Base_Folder)) + (Alire.Directories.Enter + (if Local + then Cmd.Root.Path + else R.Base_Folder)) with Unreferenced; begin for Action of R.On_Platform_Actions @@ -319,8 +355,8 @@ package body Alr.Commands.Test is Start := Clock; - Is_Available := R.Is_Available (Platform.Properties); - Is_Resolvable := Query.Is_Resolvable + Is_Available := Local or else R.Is_Available (Platform.Properties); + Is_Resolvable := Local or else Query.Is_Resolvable (R.Dependencies (Platform.Properties), Platform.Properties, Alire.Solutions.Empty_Valid_Solution); @@ -331,7 +367,7 @@ package body Alr.Commands.Test is Some_Failed := True; Reporters.End_Test (R, Testing.Unresolvable, Clock - Start, No_Log); - elsif not R.Origin.Is_System and then + elsif not Local and then not R.Origin.Is_System and then Ada.Directories.Exists (R.Base_Folder) and then not Cmd.Redo then @@ -371,23 +407,41 @@ package body Alr.Commands.Test is end; end if; - Make_Dir - (Create (+R.Base_Folder) - / Create (+Paths.Working_Folder_Inside_Root)); - -- Might not exist for system/failed/skipped - Output.Write (R.Base_Folder - / Paths.Working_Folder_Inside_Root - / "alr_test_" & Timestamp & ".log"); + if not Local then + Make_Dir + (Create (+R.Base_Folder) + / Create (+Paths.Working_Folder_Inside_Root)); + -- Might not exist for system/failed/skipped + end if; + + -- For local testing we can already use the local 'alire' folder. For + -- batch testing instead we create one folder per release. + declare + Common_Path : constant Alire.Relative_Path := + Paths.Working_Folder_Inside_Root + / Test_Name & ".log"; + begin + Output.Write (if Local + then Common_Path + else R.Base_Folder / Common_Path); + end; end Test_Release; begin - Reporters.Add (Testing.Console.New_Reporter); + if not Local then + -- These don't make much sense for single crate testing + Reporters.Add (Testing.Console.New_Reporter); + Reporters.Add (Testing.Markdown.New_Reporter); + Reporters.Add (Testing.Text.New_Reporter); + end if; + Reporters.Add (Testing.JUnit.New_Reporter); - Reporters.Add (Testing.Markdown.New_Reporter); - Reporters.Add (Testing.Text.New_Reporter); - Reporters.Start_Run ("alr_test_" & Timestamp, - Natural (Releases.Length)); + Reporters.Start_Run + ((if Local + then Cmd.Root.Working_Folder / Test_Name + else Test_Name), + Natural (Releases.Length)); declare Old_Level : constant Simple_Logging.Levels := Alire.Log_Level; @@ -401,9 +455,13 @@ package body Alr.Commands.Test is Alire.Log_Level := Simple_Logging.Warning; end if; - for R of Releases loop - Test_Release (R); - end loop; + if Local then + Test_Release (Cmd.Root.Release); + else + for R of Releases loop + Test_Release (R); + end loop; + end if; Alire.Log_Level := Old_Level; end; @@ -411,7 +469,23 @@ package body Alr.Commands.Test is Reporters.End_Run; if Some_Failed then - Reportaise_Command_Failed ("Some releases failed to pass testing"); + if Local then + Reportaise_Command_Failed + (Alire.Errors.Wrap ( + "Local test of " + & Cmd.Root.Release.Milestone.TTY_Image & " failed.", + "Check " & Alire.TTY.URL + (Alire.Paths.Working_Folder_Inside_Root + / Test_Name & ".log") + & " for details.")); + else + Reportaise_Command_Failed ("Some releases failed to pass testing"); + end if; + elsif Local then + Alire.Put_Success ("Test ended successfully."); + Alire.Put_Info ("Check log at " + & TTY.URL (Cmd.Root.Working_Folder / Test_Name + & ".log")); end if; end Do_Test; @@ -423,7 +497,11 @@ package body Alr.Commands.Test is procedure Execute (Cmd : in out Command; Args : AAA.Strings.Vector) is - Test_All : constant Boolean := Args.Count = 0; + No_Args : constant Boolean := Args.Count = 0; + + --------------- + -- Not_Empty -- + --------------- procedure Not_Empty (Item : Ada.Directories.Directory_Entry_Type; Stop : in out Boolean) @@ -464,10 +542,10 @@ package body Alr.Commands.Test is -- need to match the search term against crate names. Otherwise, we -- can directly retrieve the given crates. - if Test_All or else Cmd.Search then + if No_Args or else Cmd.Search then for Crate of Alire.Index.All_Crates.all loop if not Crate.Releases.Is_Empty then - if Test_All or else Is_Match (Crate.Name) then + if No_Args or else Is_Match (Crate.Name) then if Cmd.Last then Candidates.Include (Crate.Releases.Last_Element); else @@ -557,20 +635,28 @@ package body Alr.Commands.Test is ("Either use --full or specify crate names, but not both"); end if; - -- Check in empty folder! - if Cmd.Cont then - Trace.Detail ("Resuming tests"); - elsif Cmd.Redo then - Trace.Detail ("Redoing tests"); - else - Alire.Directories.Traverse_Tree - (Ada.Directories.Current_Directory, Not_Empty'Access); + -- For now, don't allow docker with local test + if No_Args and Cmd.Docker.all /= No_Docker then + Reportaise_Wrong_Arguments + ("Docker local testing is not yet supported."); + end if; + + -- When doing testing over index contents, we request an empty dir + if not No_Args then + if Cmd.Cont then + Trace.Detail ("Resuming tests"); + elsif Cmd.Redo then + Trace.Detail ("Redoing tests"); + else + Alire.Directories.Traverse_Tree + (Ada.Directories.Current_Directory, Not_Empty'Access); + end if; end if; CLIC.User_Input.Not_Interactive := True; -- Start testing - if Test_All then + if No_Args then if Cmd.Full then if Cmd.Last then Trace.Detail ("Testing newest release of every crate"); @@ -578,24 +664,34 @@ package body Alr.Commands.Test is Trace.Detail ("Testing all releases"); end if; else - Reportaise_Command_Failed - ("No releases specified; use --full to test'em all!"); + if Cmd.Has_Root then + Alire.Put_Info ("Testing local crate: " + & Cmd.Root.Release.Milestone.TTY_Image); + else + Reportaise_Wrong_Arguments + ("Not inside a local crate and no releases specified " + & "(use --full to test'em all!)"); + end if; end if; end if; -- Pre-find candidates to not have duplicate tests if overlapping -- requested. - Find_Candidates; - - if Candidates.Is_Empty then - Reportaise_Command_Failed ("No releases for the requested crates"); + if No_Args then + Candidates.Include (Cmd.Root.Release); else - Trace.Detail ("Testing" & Candidates.Length'Img & " releases"); + Find_Candidates; + + if Candidates.Is_Empty then + Reportaise_Command_Failed ("No releases for the requested crates"); + else + Trace.Detail ("Testing" & Candidates.Length'Img & " releases"); + end if; end if; Pull_Docker; - Do_Test (Cmd, Candidates, Docker_Image); + Do_Test (Cmd, Candidates, No_Args, Docker_Image); end Execute; ---------------------- diff --git a/src/alr/alr-commands-test.ads b/src/alr/alr-commands-test.ads index 39fb5c726..ad33bbdb4 100644 --- a/src/alr/alr-commands-test.ads +++ b/src/alr/alr-commands-test.ads @@ -33,9 +33,11 @@ package Alr.Commands.Test is private + No_Docker : constant String := "unset"; + type Command is new Commands.Command with record Cont : aliased Boolean := False; - Docker : aliased GNAT.Strings.String_Access; + Docker : aliased GNAT.Strings.String_Access := new String'(No_Docker); Full : aliased Boolean := False; Last : aliased Boolean := False; Redo : aliased Boolean := False; diff --git a/src/alr/alr-commands.adb b/src/alr/alr-commands.adb index 41e0abaf5..29e41e945 100644 --- a/src/alr/alr-commands.adb +++ b/src/alr/alr-commands.adb @@ -543,6 +543,19 @@ package body Alr.Commands is .Append ("crate~version" & ASCII.HT & "Minor-compatible version"); end Crate_Version_Sets; + -------------- + -- Has_Root -- + -------------- + + function Has_Root (Cmd : in out Command'Class) return Boolean is + begin + Cmd.Requires_Valid_Session; + return True; + exception + when Alire.Checked_Error => + return False; + end Has_Root; + ---------- -- Root -- ---------- diff --git a/src/alr/alr-commands.ads b/src/alr/alr-commands.ads index 2c54046f9..b999c9038 100644 --- a/src/alr/alr-commands.ads +++ b/src/alr/alr-commands.ads @@ -68,6 +68,9 @@ package Alr.Commands is -- performing a silent update. If not Sync, only a minimal empty lockfile -- is created. If Error, replace the first generic error message with it. + function Has_Root (Cmd : in out Command'Class) return Boolean; + -- True when Requires_Valid_Session would succeed, false otherwise + procedure Load (Cmd : Command'Class; Crate : Alire.Crate_Name; Externals : Boolean := False; diff --git a/testsuite/drivers/asserts.py b/testsuite/drivers/asserts.py index bb95002e8..c004ccdf8 100644 --- a/testsuite/drivers/asserts.py +++ b/testsuite/drivers/asserts.py @@ -95,4 +95,21 @@ def assert_installed(prefix : str, milestones : List[str]): assert_eq(f"Installation prefix found at {prefix}\n" "Contents:\n" " " + "\n ".join(milestones) + "\n", - p.out) \ No newline at end of file + p.out) + + +def assert_file_exists(path : str): + """ + Check that a file exists + """ + assert os.path.exists(path), f"Missing expected file {path}" + + +def assert_in_file(path : str, expected : str): + """ + Check that a file contains a string + """ + with open(path, "r") as f: + contents = f.read() + assert expected in contents, \ + f"Missing expected string '{expected}' in file {path}:\n{contents}" \ No newline at end of file diff --git a/testsuite/tests/test/default-test/test.py b/testsuite/tests/test/default-test/test.py index 230d436b1..69f196898 100644 --- a/testsuite/tests/test/default-test/test.py +++ b/testsuite/tests/test/default-test/test.py @@ -6,7 +6,7 @@ import os from drivers.alr import run_alr -from drivers.asserts import assert_match +from drivers.asserts import assert_match, assert_in_file from drivers.helpers import content_of from glob import glob @@ -22,4 +22,8 @@ re.escape("pass:hello=1.0.1") + ".*", content_of(glob("*.txt")[0])) +# Check the build is performed in release mode +assert_in_file(os.path.join(glob("hello_1.0.1_*")[0], "config", "hello_config.gpr"), + 'Build_Profile : Build_Profile_Kind := "release";') + print('SUCCESS') diff --git a/testsuite/tests/test/local-release/test.py b/testsuite/tests/test/local-release/test.py new file mode 100644 index 000000000..0a42f129b --- /dev/null +++ b/testsuite/tests/test/local-release/test.py @@ -0,0 +1,46 @@ +""" +Check `alr test` of the local release +""" + +import os + +from drivers.alr import add_action, init_local_crate, run_alr +from drivers.asserts import assert_file_exists, assert_in_file + +# Create a crate with a local release +init_local_crate() +run_alr("test") # Ending with success is enough + +# Check the expected log files exist +assert_file_exists(os.path.join("alire", "alr_test_local.log")) +assert_file_exists(os.path.join("alire", "alr_test_local.xml")) + +# Check the build is performed in release mode +assert_in_file(os.path.join("config", "xxx_config.gpr"), + 'Build_Profile : Build_Profile_Kind := "release";') + +# Check testing from a subdirectory in a new crate +os.chdir("..") +init_local_crate("yyy") +os.chdir("src") +run_alr("test") # Ending with success is enough + +# Check the expected log files exist +assert_file_exists(os.path.join("..", "alire", "alr_test_local.log")) +assert_file_exists(os.path.join("..", "alire", "alr_test_local.xml")) + +# Check testing with a test action instead of default build +os.chdir("..") +init_local_crate("zzz") +add_action("test", ["touch", "success.txt"]) +run_alr("test") +assert_file_exists("success.txt") + +# Likewise from a subdirectory +os.remove("success.txt") +os.chdir("src") +run_alr("test") +assert_file_exists(os.path.join("..", "success.txt")) + + +print('SUCCESS') diff --git a/testsuite/tests/test/local-release/test.yaml b/testsuite/tests/test/local-release/test.yaml new file mode 100644 index 000000000..32c747b3f --- /dev/null +++ b/testsuite/tests/test/local-release/test.yaml @@ -0,0 +1 @@ +driver: python-script