Repository: conan-io/conan

Issue:
[feature] Improve cache check-integrity output
### What is your suggestion?

After implementing https://github.com/conan-io/conan/pull/18527, I found an issue with the output of this command.

Currently, the output of the `conan cache check-integrity` command is somewhat confusing, as it does not display the revisions of the recipe and packages, making it unclear which package it is referring to.

```
$ conan cache check-integrity hello*
Found 3 pkg/version recipes matching hello* in local cache
hello/0.1: Integrity checked: ok
hello/0.1:dee9f7f985eb1c20e3c41afaa8c35e2a34b5ae0b: Integrity checked: ok
hello/0.1: Integrity checked: ok
hello/0.1:522dcea5982a3f8a5b624c16477e47195da2f84f: Integrity checked: ok
hello/0.1: Integrity checked: ok
hello/0.1:522dcea5982a3f8a5b624c16477e47195da2f84f: Integrity checked: ok
hello/0.1: Integrity checked: ok
hello/0.1:522dcea5982a3f8a5b624c16477e47195da2f84f: Integrity checked: ok
hello/0.1:522dcea5982a3f8a5b624c16477e47195da2f84f: Integrity checked: ok
hello/0.1:522dcea5982a3f8a5b624c16477e47195da2f84f: Integrity checked: ok
hello/0.1: Integrity checked: ok
hello/0.1:63fead0844576fc02943e16909f08fcdddd6f44b: Integrity checked: ok
...
```

I think the output should include the revisions, like:

```
hello/0.1#fesf46f56s6f7ef7ad123fesf46f56s6f7ef7ad123a: Integrity checked: ok
hello/0.1#fesf46f56s6f7ef7ad123fesf46f56s6f7ef7ad123a:63fead0844576fc02943e16909f08fcdddd6f44b#ae65f56e5fa5ee5afe65f6ea5fee52132faedfdaa7: Integrity checked: ok
```

As this is the reference that could be used to check the issue if the check integrity fails:

```
$ conan cache path hello/0.1#fesf46f56s6f7ef7ad123fesf46f56s6f7ef7ad123a:63fead0844576fc02943e16909f08fcdddd6f44b#ae65f56e5fa5ee5afe65f6ea5fee52132faedfdaa7
```

cc/ @AbrilRBS 

### Have you read the CONTRIBUTING guide?

- [x] I've read the CONTRIBUTING guide


Discussion:
I agree this can be improved, lets do it.



Patch:
diff --git a/conan/internal/cache/integrity_check.py b/conan/internal/cache/integrity_check.py
index b1148e612aa..43b526d11f6 100644
--- a/conan/internal/cache/integrity_check.py
+++ b/conan/internal/cache/integrity_check.py
@@ -31,11 +31,11 @@ def check(self, pkg_list):
 
     def _recipe_corrupted(self, ref: RecipeReference):
         layout = self._cache.recipe_layout(ref)
-        output = ConanOutput()
+        output = ConanOutput(scope=f"{ref.repr_notime()}")
         try:
             read_manifest, expected_manifest = layout.recipe_manifests()
         except FileNotFoundError:
-            output.error(f"{ref.repr_notime()}: Manifest missing", error_type="exception")
+            output.error("Manifest missing", error_type="exception")
             return True
         # Filter exports_sources from read manifest if there are no exports_sources locally
         # This happens when recipe is downloaded without sources (not built from source)
@@ -45,28 +45,28 @@ def _recipe_corrupted(self, ref: RecipeReference):
                                        if not k.startswith("export_source")}
 
         if read_manifest != expected_manifest:
-            output.error(f"{ref}: Manifest mismatch", error_type="exception")
-            output.error(f"Folder: {layout.export()}", error_type="exception")
+            output_lines = ["", "Manifest mismatch", f"    Folder: {layout.package()}"]
             diff = read_manifest.difference(expected_manifest)
             for fname, (h1, h2) in diff.items():
-                output.error(f"    '{fname}' (manifest: {h1}, file: {h2})", error_type="exception")
+                output_lines.append(f"        {fname} (manifest: {h1}, file: {h2})")
+            output.error("\n".join(output_lines), error_type="exception")
             return True
-        output.info(f"{ref}: Integrity checked: ok")
+        output.info("Integrity check: ok")
 
     def _package_corrupted(self, ref: PkgReference):
         layout = self._cache.pkg_layout(ref)
-        output = ConanOutput()
+        output = ConanOutput(scope=f"{ref.repr_notime()}")
         try:
             read_manifest, expected_manifest = layout.package_manifests()
         except FileNotFoundError:
-            output.error(f"{ref.repr_notime()}: Manifest missing", error_type="exception")
+            output.error("Manifest missing", error_type="exception")
             return True
 
         if read_manifest != expected_manifest:
-            output.error(f"{ref}: Manifest mismatch", error_type="exception")
-            output.error(f"Folder: {layout.package()}", error_type="exception")
+            output_lines = ["", "Manifest mismatch", f"    Folder: {layout.package()}"]
             diff = read_manifest.difference(expected_manifest)
             for fname, (h1, h2) in diff.items():
-                output.error(f"    '{fname}' (manifest: {h1}, file: {h2})", error_type="exception")
+                output_lines.append(f"        {fname} (manifest: {h1}, file: {h2})")
+            output.error("\n".join(output_lines), error_type="exception")
             return True
-        output.info(f"{ref}: Integrity checked: ok")
+        output.info("Integrity check: ok")


Test patch:
diff --git a/test/integration/command/cache/test_cache_integrity.py b/test/integration/command/cache/test_cache_integrity.py
index cbbf69a613c..b40a2f9a676 100644
--- a/test/integration/command/cache/test_cache_integrity.py
+++ b/test/integration/command/cache/test_cache_integrity.py
@@ -30,20 +30,24 @@ def test_cache_integrity(use_pkglist):
     arg = "--list=pkglist.json" if use_pkglist else "*"
 
     t.run(f"cache check-integrity {arg}", assert_error=True)
-    assert "pkg1/1.0: Integrity checked: ok" in t.out
-    assert "pkg1/1.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Integrity checked: ok" in t.out
-    assert "ERROR: pkg2/2.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Manifest mismatch" in t.out
-    assert "ERROR: pkg3/3.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Manifest mismatch" in t.out
-    assert "ERROR: pkg4/4.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Manifest mismatch" in t.out
+    assert "pkg1/1.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
+    assert "pkg1/1.0#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709" \
+           "#0ba8627bd47edc3a501e8f0eb9a79e5e: Integrity check: ok" in t.out
+    assert "pkg2/2.0#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709" \
+           "#0ba8627bd47edc3a501e8f0eb9a79e5e: ERROR: \nManifest mismatch" in t.out
+    assert "pkg3/3.0#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709" \
+           "#0ba8627bd47edc3a501e8f0eb9a79e5e: ERROR: \nManifest mismatch" in t.out
+    assert "pkg4/4.0#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709" \
+           "#0ba8627bd47edc3a501e8f0eb9a79e5e: ERROR: \nManifest mismatch" in t.out
 
     t.run("remove pkg2/2.0:da39a3ee5e6b4b0d3255bfef95601890afd80709 -c")
     t.run("remove pkg3/3.0:da39a3ee5e6b4b0d3255bfef95601890afd80709 -c")
     t.run("remove pkg4/4.0:da39a3ee5e6b4b0d3255bfef95601890afd80709 -c")
     t.run("cache check-integrity *")
-    assert "pkg1/1.0: Integrity checked: ok" in t.out
-    assert "pkg2/2.0: Integrity checked: ok" in t.out
-    assert "pkg3/3.0: Integrity checked: ok" in t.out
-    assert "pkg4/4.0: Integrity checked: ok" in t.out
+    assert "pkg1/1.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
+    assert "pkg2/2.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
+    assert "pkg3/3.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
+    assert "pkg4/4.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
 
 
 def test_cache_integrity_missing_recipe_manifest():
@@ -57,15 +61,16 @@ def test_cache_integrity_missing_recipe_manifest():
     t.run("create . --name pkg3 --version=3.0")
 
     t.run("cache check-integrity *", assert_error=True)
-    assert "pkg1/1.0: Integrity checked: ok" in t.out
-    assert "ERROR: pkg2/2.0#4d670581ccb765839f2239cc8dff8fbd: Manifest missing" in t.out
-    assert "pkg3/3.0: Integrity checked: ok" in t.out
+    assert "pkg1/1.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
+    assert "pkg2/2.0#4d670581ccb765839f2239cc8dff8fbd: ERROR: Manifest missing" in t.out
+    assert "pkg3/3.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
     assert "ERROR: There are corrupted artifacts, check the error logs" in t.out
 
     t.run("remove pkg2* -c")
     t.run("cache check-integrity *")
-    assert "pkg1/1.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Integrity checked: ok" in t.out
-    assert "pkg3/3.0: Integrity checked: ok" in t.out
+    assert "pkg1/1.0#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709" \
+           "#0ba8627bd47edc3a501e8f0eb9a79e5e: Integrity check: ok" in t.out
+    assert "pkg3/3.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
     assert "Integrity check: ok" in t.out
 
 
@@ -80,17 +85,17 @@ def test_cache_integrity_missing_package_manifest():
     t.run("create . --name pkg3 --version=3.0")
 
     t.run("cache check-integrity *", assert_error=True)
-    assert "pkg1/1.0: Integrity checked: ok" in t.out
-    assert "ERROR: pkg2/2.0#4d670581ccb765839f2239cc8dff8fbd" \
-           ":da39a3ee5e6b4b0d3255bfef95601890afd80709" \
-           "#0ba8627bd47edc3a501e8f0eb9a79e5e: Manifest missing" in t.out
-    assert "pkg3/3.0: Integrity checked: ok" in t.out
+    assert "pkg1/1.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
+    assert "pkg2/2.0#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709" \
+           "#0ba8627bd47edc3a501e8f0eb9a79e5e: ERROR: Manifest missing" in t.out
+    assert "pkg3/3.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
     assert "ERROR: There are corrupted artifacts, check the error logs" in t.out
 
     t.run("remove pkg2* -c")
     t.run("cache check-integrity *")
-    assert "pkg1/1.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Integrity checked: ok" in t.out
-    assert "pkg3/3.0: Integrity checked: ok" in t.out
+    assert "pkg1/1.0#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709" \
+           "#0ba8627bd47edc3a501e8f0eb9a79e5e: Integrity check: ok" in t.out
+    assert "pkg3/3.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
     assert "Integrity check: ok" in t.out
 
 
@@ -104,12 +109,26 @@ def test_cache_integrity_missing_package_conaninfo():
     os.remove(conaninfo)
 
     t.run("cache check-integrity *", assert_error=True)
-    assert "pkg1/1.0: Integrity checked: ok" in t.out
-    assert "ERROR: pkg2/2.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Manifest mismatch" in t.out
+    assert "pkg1/1.0#4d670581ccb765839f2239cc8dff8fbd: Integrity check: ok" in t.out
+    assert "pkg2/2.0#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709" \
+           "#0ba8627bd47edc3a501e8f0eb9a79e5e: ERROR: \nManifest mismatch" in t.out
 
     t.run("remove pkg2* -c")
     t.run("cache check-integrity *")
-    assert "pkg1/1.0:da39a3ee5e6b4b0d3255bfef95601890afd80709: Integrity checked: ok" in t.out
+    assert "pkg1/1.0#4d670581ccb765839f2239cc8dff8fbd:da39a3ee5e6b4b0d3255bfef95601890afd80709" \
+           "#0ba8627bd47edc3a501e8f0eb9a79e5e: Integrity check: ok" in t.out
+
+
+def test_cache_integrity_missing_package_file():
+    t = TestClient()
+    t.save({"conanfile.py": GenConanfile().with_package_file("myfile", "mycontents!")})
+    t.run("create . --name pkg --version 1.0")
+    layout = t.created_layout()
+    os.remove(os.path.join(layout.package(), "myfile"))
+
+    t.run("cache check-integrity *", assert_error=True)
+    assert "pkg/1.0#2f2609c8e5c87bf836c3fdaa6096b55d:da39a3ee5e6b4b0d3255bfef95601890afd80709" \
+           "#d950d0cd76f6bba62c8add9c68d1aeb3: ERROR: \nManifest mismatch" in t.out
 
 
 def test_cache_integrity_export_sources():
@@ -119,7 +138,7 @@ def test_cache_integrity_export_sources():
             "src/mysource.cpp": ""})
     t.run("create .")
     t.run("cache check-integrity *")
-    assert "pkg/0.1: Integrity checked: ok" in t.out
+    assert "pkg/0.1#ae07161b81ab07f7f1f746391668df0e: Integrity check: ok" in t.out
 
     # If we download, integrity should be ok
     # (it failed before, because the manifest is not complete)
@@ -127,4 +146,4 @@ def test_cache_integrity_export_sources():
     t.run("remove * -c")
     t.run("install --requires=pkg/0.1")
     t.run("cache check-integrity *")
-    assert "pkg/0.1: Integrity checked: ok" in t.out
+    assert "pkg/0.1#ae07161b81ab07f7f1f746391668df0e: Integrity check: ok" in t.out
diff --git a/test/integration/command/upload/upload_test.py b/test/integration/command/upload/upload_test.py
index 45e64ebe318..e8f6adcb004 100644
--- a/test/integration/command/upload/upload_test.py
+++ b/test/integration/command/upload/upload_test.py
@@ -177,8 +177,13 @@ def test_corrupt_upload(self):
         save(os.path.join(package_folder, "added.txt"), "")
         os.remove(os.path.join(package_folder, "include/hello.h"))
         client.run("upload hello0/1.2.1@frodo/stable --check -r default", assert_error=True)
-        self.assertIn("ERROR:     'include/hello.h'", client.out)
-        self.assertIn("ERROR:     'added.txt'", client.out)
+        self.assertIn("hello0/1.2.1@frodo/stable#3afd661184b94bdac7fb2057e7bd9baa"
+                      ":da39a3ee5e6b4b0d3255bfef95601890afd80709"
+                      "#e70e86439dec07a0d5d3414648b0b16c: ERROR", client.out)
+        self.assertIn("include/hello.h (manifest: d41d8cd98f00b204e9800998ecf8427e, file: None)",
+                      client.out)
+        self.assertIn("added.txt (manifest: None, file: d41d8cd98f00b204e9800998ecf8427e)",
+                      client.out)
         self.assertIn("ERROR: There are corrupted artifacts, check the error logs", client.out)
 
     @pytest.mark.artifactory_ready
diff --git a/test/integration/conanfile/test_finalize_method.py b/test/integration/conanfile/test_finalize_method.py
index fe3f0363f60..c18d79b34f8 100644
--- a/test/integration/conanfile/test_finalize_method.py
+++ b/test/integration/conanfile/test_finalize_method.py
@@ -364,5 +364,6 @@ def test_upload_verify_integrity(self, client):
         client.run("create dep")
         dep_layout = client.created_layout()
         client.run("upload * -r=default -c --check")
-        assert f"dep/1.0:{dep_layout.reference.package_id}: Integrity checked: ok" in client.out
+        assert f"dep/1.0#{dep_layout.reference.ref.revision}:{dep_layout.reference.package_id}" \
+               f"#{dep_layout.reference.revision}: Integrity check: ok" in client.out
         assert "There are corrupted artifacts" not in client.out