Repository: spack/spack

Issue:
"self.run_tests" attribute masked by the object wrapper
### Steps to reproduce

Originally reported by @rbberger in https://spackpm.slack.com/archives/C5VL7V81G/p1669578286178559 

Classes that try to use `self.run_tests` to set any custom attribute and are not using builders will see this attribute set to `False` due to the object wrapper.

### Error message

No error message, just unexpected behavior.

### Information on your system

* **Spack:** 0.20.0.dev0 (e030833129337f080c19e953af73624ddd2a16c5)
* **Python:** 3.8.10
* **Platform:** linux-ubuntu20.04-icelake
* **Concretizer:** clingo

### General information

- [X] I have run `spack debug report` and reported the version of Spack/Python/Platform
- [X] I have searched the issues of this repo and believe this is not a duplicate
- [X] I have run the failing commands in debug mode and reported the output


Discussion:
I was trying to reproduce this problem on my macbook, and for some reason it seems to work fine on macOS. Shall I try to make a reproducible example?
This simple example _works_ on macOS and _fails_ on Linux:

```
class RunTestsTest(Package):
    """Test Package"""

    homepage = "http://example.com"
    url      = "https://downloads.sourceforge.net/project/bowtie-bio/bowtie/1.2.1.1/bowtie-1.2.1.1-src.zip"
    version('1.2.1.1', 'ec06265730c5f587cd58bcfef6697ddf')

    def install(self, spec, prefix):
        print("run_tests = ", self.run_tests)
        assert False
```

On Linux:
```
$ spack -vd install --test=root run-tests-test 2>&1 | grep -A1 run_test
run_tests =  False
==> [2022-12-14-15:11:47.854487] Error: AssertionError:
--
          9        print("run_tests = ", self.run_tests)
  >>     10        assert False
(base) [/scratch/vanderaa]
 $ spack-python --version
Python 3.10.6
$ spack --version
0.20.0.dev0 (eec09f791d320c6ba73be0a71a8f0e0f4355a31c)
```

On macOS:

```
 $ spack -vd install --test=root run-tests-test 2>&1 | grep -A1 run_tests
run_tests =  True
==> [2022-12-14-14:37:46.526235] Error: AssertionError:
--
          9        print("run_tests = ", self.run_tests)
  >>     10        assert False
 $ spack-python --version
Python 3.10.8
$ spack --version
0.20.0.dev0 (9fdb36585f083133232686015903c022ea653980)
```
The issue is due to this line:

https://github.com/spack/spack/blob/55356e9edb374bc2e547ed1706dace6c34042dd9/lib/spack/spack/installer.py#L1276

which monkey patches an object that was used, before monkey patching, to create and _cache_ a builder. The wrapping is done here:

https://github.com/spack/spack/blob/55356e9edb374bc2e547ed1706dace6c34042dd9/lib/spack/spack/builder.py#L130

Resolving patch:
diff --git a/lib/spack/spack/builder.py b/lib/spack/spack/builder.py
--- a/lib/spack/spack/builder.py
+++ b/lib/spack/spack/builder.py
@@ -124,7 +124,12 @@ def __init__(self, wrapped_pkg_object, root_builder):
             wrapper_cls = type(self)
             bases = (package_cls, wrapper_cls)
             new_cls_name = package_cls.__name__ + "Wrapper"
-            new_cls = type(new_cls_name, bases, {})
+            # Forward attributes that might be monkey patched later
+            new_cls = type(
+                new_cls_name,
+                bases,
+                {"run_tests": property(lambda x: x.wrapped_package_object.run_tests)},
+            )
             new_cls.__module__ = package_cls.__module__
             self.__class__ = new_cls
             self.__dict__.update(wrapped_pkg_object.__dict__)


Test patch:
diff --git a/lib/spack/spack/test/builder.py b/lib/spack/spack/test/builder.py
--- a/lib/spack/spack/test/builder.py
+++ b/lib/spack/spack/test/builder.py
@@ -140,3 +140,17 @@ def test_build_time_tests_are_executed_from_default_builder():
 
     assert os.environ.get("CHECK_CALLED") == "1", "Build time tests not executed"
     assert os.environ.get("INSTALLCHECK_CALLED") == "1", "Install time tests not executed"
+
+
+@pytest.mark.regression("34518")
+@pytest.mark.usefixtures("builder_test_repository", "config", "working_env")
+def test_monkey_patching_wrapped_pkg():
+    s = spack.spec.Spec("old-style-autotools").concretized()
+    builder = spack.builder.create(s.package)
+    assert s.package.run_tests is False
+    assert builder.pkg.run_tests is False
+    assert builder.pkg_with_dispatcher.run_tests is False
+
+    s.package.run_tests = True
+    assert builder.pkg.run_tests is True
+    assert builder.pkg_with_dispatcher.run_tests is True