Repository: conda/conda-build

Issue:
build environment prefix is not replaced
If PREFIX is detected in a file it is a replaced by a sentinel which is then patched when the file is installed into a conda environment.  This logic is not applied to BUILD_PREFIX, and files containing this path may be included in the package.  These paths will be invalid when installing these packages into an environment but because the build environment is not moved prior to the testing phase, #2625, they are still valid at test time.  Running the tests post build will fail as the build environment is deleted after a successful build.

Example

meta.yaml
``` yaml
package:
  name: compiler_wrapper
  version: 1.0.0

requirements:
  build:
    - {{ compiler('c') }}
  run:
    - {{ compiler('c') }}

test:
  commands:
    - wrapper.sh --version
```

build.sh
``` bash
mkdir -p ${PREFIX}/bin

cat > ${PREFIX}/bin/wrapper.sh <<EOF
#! /bin/sh
echo "Hello from the compiler wrapper"
echo "The compiler version is:"
${CC} \$@
EOF

chmod +x ${PREFIX}/bin/wrapper.sh

echo "At build time the wrapper script is:"
echo "-----------------------------------"
cat ${PREFIX}/bin/wrapper.sh
echo "-----------------------------------"
```

Initial build:
```
$ conda build .
Adding in variants from internal_defaults
...
At build time the wrapper script is:
-----------------------------------
#! /bin/sh
echo "Hello from the compiler wrapper"
echo "The compiler version is:"
/home/jhelmus/anaconda3/conda-bld/compiler_wrapper_1518127856653/_build_env/bin/x86_64-conda_cos6-linux-gnu-cc $@
...
TEST START: /home/jhelmus/anaconda3/conda-bld/linux-64/compiler_wrapper-1.0.0-h14c3975_0.tar.bz2
...
+ wrapper.sh --version
Hello from the compiler wrapper
The compiler version is:
x86_64-conda_cos6-linux-gnu-cc (crosstool-NG 8a21ab48) 7.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

+ exit 0
TEST END: /home/jhelmus/anaconda3/conda-bld/linux-64/compiler_wrapper-1.0.0-h14c3975_0.tar.bz2
```

Post build test:
```
$ conda build --test /home/jhelmus/anaconda3/conda-bld/linux-64/compiler_wrapper-1.0.0-h14c3975_0.tar.bz2
TEST START: /home/jhelmus/anaconda3/conda-bld/linux-64/compiler_wrapper-1.0.0-h14c3975_0.tar.bz2
...
+ wrapper.sh --version
Hello from the compiler wrapper
The compiler version is:
/home/jhelmus/anaconda3/conda-bld/compiler_wrapper_1518127960250/_test_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_/bin/wrapper.sh: 4: /home/jhelmus/anaconda3/conda-bld/compiler_wrapper_1518127960250/_test_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_/bin/wrapper.sh: /home/jhelmus/anaconda3/conda-bld/compiler_wrapper_1518127856653/_build_env/bin/x86_64-conda_cos6-linux-gnu-cc: not found

Leaving build/test directories:
  Work:  /home/jhelmus/anaconda3/conda-bld/work 
  Test:  /home/jhelmus/anaconda3/conda-bld/test_tmp 
Leaving build/test environments:
  Test: source activate  /home/jhelmus/anaconda3/conda-bld/_test_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_p 
  Build:        source activate  /home/jhelmus/anaconda3/conda-bld/_build_env 


TESTS FAILED: compiler_wrapper-1.0.0-h14c3975_0.tar.bz2

$ cat /home/jhelmus/anaconda3/conda-bld/compiler_wrapper_1518127960250/_test_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_/bin/wrapper.sh
#! /bin/sh
echo "Hello from the compiler wrapper"
echo "The compiler version is:"
/home/jhelmus/anaconda3/conda-bld/compiler_wrapper_1518127856653/_build_env/bin/x86_64-conda_cos6-linux-gnu-cc $@
```





Discussion:
I'm totally fine fixing this so that both host_prefix and build_prefix are detected and treated similarly.  Are there any cases we foresee where doing so may break things?
build_prefix is not filled to 255 characters so there may be issues if this is detected in a binary file.
hm, yes.  That's an excellent point.  We can't just straight up replace it wholesale.  I'll need to think about that for a while.  It seems like when we get ahold of things, it'll already be too late to fix that length.
Maybe the easiest thing is to make the build prefix also padded?

Conda records the prefix placeholder, no matter what it is, but the only way to reserve the space is by having the padded prefix.  I think we should pad the build prefix, then scan and replace the build prefix in all files with the host prefix, and finally let conda do the replacement of only host prefix at install time.
The command line argument `--merge-build-host` can be used as a workaround.
I would be alright if the build prefix was padded.  It will make the build directory a bit of an eyesore but it is needed to reserve the space in binary files.  
@mingwandroid @nehaljwani any comment here?
I prefer the metadata `build/merge-build-host` instead of the command-line argument since the need for the workaround is a property of the thing being built.
In a lot of cases of build tool paths being embedded I was able to instead use just the filename:

`CC=$(basename ${CC})`

.. when this can work it is preferable because prefix replacement comes with costs. I've yet to run into a hard need to do build prefix replacement (not saying it will not happen though).

If we need to pad to 255 then we need to pad to 255.

Should we generally set CC to that value in our compiler activation scripts?  Would there be any downside to that?
So long as it's on PATH we can do it that way I think.
@jjhelmus Ray's proposal would fix your example here.  Is your example representative enough of the problems in hdf5 that you feel like changing CC would also fix the real-world breakages you saw?
Either `CC=$(basename ${CC})` or merging the build and host environments via flags or a meta.yaml entry will fix the issue I was seeing and are fine solutions.  

I do worry that it is rather difficult to detect files which accidentally include a build prefix since the build environment is still a valid path during the test phase.  Moving the build environment prior to the test phase would result in these tests failing and easier detection of such path inclusions.
Great, I think setting CC is the least disruptive (though also perhaps the most likely to break in the future, if we missed some tool that gets recorded somehow.

I will change CC and CXX for now.

Moving the build env prior to test is easy.  I'll make that change.
We should adjust the compiler activation scripts I think?
I'm not sure if CC should exclude the path in the activation script.  There are a few instances that I know of where tools derive the path of other toolchain components from CC and removing the path would break these.  I'm find with requiring recipe which need a path-less CC to include `CC=$(basename ${CC})` in the recipe.
I would have opined that we should do what's right for the majority, and then use `which $CC` for the others but since `which` was broken with long paths until recently I will not opine that.
Although if you do need the path `CC=$(type -p ${CC}` will put it back.  I don't have a strong opinion which is the better option.

Resolving patch:
diff --git a/conda_build/build.py b/conda_build/build.py
--- a/conda_build/build.py
+++ b/conda_build/build.py
@@ -940,9 +940,16 @@ def scan_metadata(path):
 
 
 def _write_sh_activation_text(file_handle, m):
-    file_handle.write('source "{0}activate" "{1}"\n'
-                .format(utils.root_script_dir + os.path.sep,
-                        m.config.build_prefix))
+    cygpath_prefix = "$(cygpath -u " if utils.on_win else ""
+    cygpath_suffix = " )" if utils.on_win else ""
+    activate_path = ''.join((cygpath_prefix,
+                            os.path.join(utils.root_script_dir, 'activate').replace('\\', '\\\\'),
+                            cygpath_suffix))
+    build_prefix_path = ''.join((cygpath_prefix,
+                                m.config.build_prefix.replace('\\', '\\\\'),
+                                cygpath_suffix))
+
+    file_handle.write('source "{0}" "{1}"\n'.format(activate_path, build_prefix_path))
 
     # conda 4.4 requires a conda-meta/history file for a valid conda prefix
     history_file = join(m.config.build_prefix, 'conda-meta', 'history')
@@ -973,9 +980,10 @@ def _write_sh_activation_text(file_handle, m):
             open(history_file, 'a').close()
         file_handle.write('unset CONDA_PATH_BACKUP\n')
         file_handle.write('export CONDA_MAX_SHLVL=2\n')
-        file_handle.write('source "{0}activate" "{1}"\n'
-                          .format(utils.root_script_dir + os.path.sep,
-                                  m.config.host_prefix))
+        host_prefix_path = ''.join((cygpath_prefix,
+                                   m.config.host_prefix.replace('\\', '\\\\'),
+                                   cygpath_suffix))
+        file_handle.write('source "{0}" "{1}"\n' .format(activate_path, host_prefix_path))
 
 
 def _write_activation_text(script_path, m):
@@ -1636,6 +1644,16 @@ def test(recipedir_or_package_or_metadata, config, move_broken=True):
         return True
 
     if metadata.config.remove_work_dir:
+        if os.path.isdir(metadata.config.build_prefix):
+            # move build folder to force hardcoded paths to build env to break during tests
+            #    (so that they can be properly addressed by recipe author)
+            dest = os.path.join(os.path.dirname(metadata.config.build_prefix),
+                        '_'.join(('build_prefix_moved', metadata.dist(),
+                                    metadata.config.host_subdir)))
+            # Needs to come after create_files in case there's test/source_files
+            print("Renaming build prefix directory, ", metadata.config.build_prefix, " to ", dest)
+            os.rename(config.build_prefix, dest)
+
         # nested if so that there's no warning when we just leave the empty workdir in place
         if metadata.source_provided:
             dest = os.path.join(os.path.dirname(metadata.config.work_dir),


Test patch:
diff --git a/tests/test-recipes/split-packages/_output_activation/install_activated.sh b/tests/test-recipes/split-packages/_output_activation/install_activated.sh
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/split-packages/_output_activation/install_activated.sh
@@ -0,0 +1 @@
+if [ -z ${ACTIVATION_VAR+x} ]; then echo "not activated, but we should be" && exit 1; fi
diff --git a/tests/test-recipes/split-packages/_output_activation/install_not_activated.sh b/tests/test-recipes/split-packages/_output_activation/install_not_activated.sh
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/split-packages/_output_activation/install_not_activated.sh
@@ -0,0 +1 @@
+if [ ! -z ${ACTIVATION_VAR+x} ]; then echo "activated, but we should not be" && exit 1; fi
diff --git a/tests/test-recipes/split-packages/_output_activation/meta.yaml b/tests/test-recipes/split-packages/_output_activation/meta.yaml
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/split-packages/_output_activation/meta.yaml
@@ -0,0 +1,32 @@
+package:
+  name: test_output_activation
+  version: 1.0
+
+outputs:
+  - name: activation_package
+    build:
+      script:
+        - mkdir %PREFIX%\etc\conda\activate.d  # [win]
+        - echo set ACTIVATION_VAR=1 > %PREFIX%\etc\conda\activate.d\activate_script.bat  # [win]
+        - mkdir -p $PREFIX/etc/conda/activate.d    # [unix]
+        - echo "export ACTIVATION_VAR=1" > $PREFIX/etc/conda/activate.d/activate_script.sh  # [unix]
+        - echo export ACTIVATION_VAR=1 > %PREFIX%\etc\conda\activate.d\activate_script.sh  # [win]
+  - name: no_activate_by_default
+    script: install_not_activated.sh
+    requirements:
+      build:
+        - {{ pin_subpackage('activation_package') }}
+  - name: build_script_activates
+    build:
+      script:
+        - if [ -z "$ACTIVATION_VAR" ]; then echo "build/script not activated" && exit 1; fi   # [unix]
+        - IF "%ACTIVATION_VAR%" == "" echo "error! activation var unset!" && exit 1             # [win]
+    requirements:
+      build:
+        - {{ pin_subpackage('activation_package') }}
+  - name: using_compiler_jinja_activates
+    requirements:
+      build:
+        - {{ compiler('c') }}
+        - {{ pin_subpackage('activation_package') }}
+    script: install_activated.sh
diff --git a/tests/test_api_build.py b/tests/test_api_build.py
--- a/tests/test_api_build.py
+++ b/tests/test_api_build.py
@@ -336,7 +336,6 @@ def test_jinja_typo(testing_workdir, testing_config):
     assert "GIT_DSECRIBE_TAG" in exc.exconly()
 
 
-@pytest.mark.serial
 def test_skip_existing(testing_workdir, testing_config, capfd):
     # build the recipe first
     api.build(empty_sections, config=testing_config)
@@ -345,7 +344,6 @@ def test_skip_existing(testing_workdir, testing_config, capfd):
     assert "are already built" in output
 
 
-@pytest.mark.serial
 def test_skip_existing_url(testing_metadata, testing_workdir, capfd):
     # make sure that it is built
     outputs = api.build(testing_metadata)
@@ -395,7 +393,6 @@ def test_requirements_txt_for_run_reqs(testing_workdir, testing_config):
     api.build(os.path.join(metadata_dir, "_requirements_txt_run_reqs"), config=testing_config)
 
 
-@pytest.mark.serial
 def test_compileall_compiles_all_good_files(testing_workdir, testing_config):
     output = api.build(os.path.join(metadata_dir, "_compile-test"), config=testing_config)[0]
     good_files = ['f1.py', 'f3.py']
@@ -681,7 +678,6 @@ def test_preferred_env(testing_config):
     assert 'package_metadata_version' in extra
 
 
-@pytest.mark.serial
 def test_skip_compile_pyc(testing_config):
     outputs = api.build(os.path.join(metadata_dir, "skip_compile_pyc"), config=testing_config)
     tf = tarfile.open(outputs[0])
@@ -796,7 +792,6 @@ def test_build_expands_wildcards(mocker, testing_workdir):
                                        post=None, variants=None)
 
 
-@pytest.mark.serial
 @pytest.mark.parametrize('set_build_id', [True, False])
 def test_remove_workdir_default(testing_config, caplog, set_build_id):
     recipe = os.path.join(metadata_dir, '_keep_work_dir')
@@ -809,7 +804,6 @@ def test_remove_workdir_default(testing_config, caplog, set_build_id):
     assert not glob(os.path.join(metadata.config.work_dir, '*'))
 
 
-@pytest.mark.serial
 def test_keep_workdir_and_dirty_reuse(testing_config, capfd):
     recipe = os.path.join(metadata_dir, '_keep_work_dir')
     # make a metadata object - otherwise the build folder is computed within the build, but does
@@ -885,7 +879,6 @@ def test_append_python_app_osx(testing_config):
 #         api.build(metadata)
 
 
-@pytest.mark.serial
 def test_run_exports(testing_metadata, testing_config, testing_workdir):
     api.build(os.path.join(metadata_dir, '_run_exports'), config=testing_config, notest=True)
     api.build(os.path.join(metadata_dir, '_run_exports_implicit_weak'), config=testing_config,
@@ -924,7 +917,6 @@ def test_run_exports(testing_metadata, testing_config, testing_workdir):
     assert 'weak_pinned_package 2.0.*' in m.meta['requirements']['run']
 
 
-@pytest.mark.serial
 def test_ignore_run_exports(testing_metadata, testing_config):
     # build the package with run exports for ensuring that we ignore it
     api.build(os.path.join(metadata_dir, '_run_exports'), config=testing_config,
@@ -957,7 +949,6 @@ def test_copy_read_only_file_with_xattr(testing_config, testing_workdir):
     api.build(recipe, config=testing_config)
 
 
-@pytest.mark.serial
 def test_env_creation_fail_exits_build(testing_config):
     recipe = os.path.join(metadata_dir, '_post_link_exits_after_retry')
     with pytest.raises((RuntimeError, LinkError, CondaError)):
@@ -968,7 +959,6 @@ def test_env_creation_fail_exits_build(testing_config):
         api.build(recipe, config=testing_config)
 
 
-@pytest.mark.serial
 def test_recursion_packages(testing_config):
     """Two packages that need to be built are listed in the recipe
 
@@ -977,7 +967,6 @@ def test_recursion_packages(testing_config):
     api.build(recipe, config=testing_config)
 
 
-@pytest.mark.serial
 def test_recursion_layers(testing_config):
     """go two 'hops' - try to build a, but a needs b, so build b first, then come back to a"""
     recipe = os.path.join(metadata_dir, '_recursive-build-two-layers')
@@ -1002,7 +991,6 @@ def test_extract_tarball_with_unicode_filename(testing_config):
     api.build(recipe, config=testing_config)
 
 
-@pytest.mark.serial
 def test_failed_recipe_leaves_folders(testing_config, testing_workdir):
     recipe = os.path.join(fail_dir, 'recursive-build')
     m = api.render(recipe, config=testing_config)[0][0]
@@ -1056,7 +1044,6 @@ def test_run_constrained_stores_constrains_info(testing_config):
     assert info_contents['constrains'][0] == 'bzip2  1.*'
 
 
-@pytest.mark.serial
 def test_no_locking(testing_config):
     recipe = os.path.join(metadata_dir, 'source_git_jinja2')
     api.update_index(os.path.join(testing_config.croot, testing_config.subdir),
diff --git a/tests/test_api_convert.py b/tests/test_api_convert.py
--- a/tests/test_api_convert.py
+++ b/tests/test_api_convert.py
@@ -148,7 +148,6 @@ def test_c_extension_conversion(testing_workdir, base_platform, package):
         assert os.path.exists('{}/{}' .format(platform, fn))
 
 
-@pytest.mark.serial
 @pytest.mark.parametrize('base_platform', ['linux', 'win', 'osx'])
 @pytest.mark.parametrize('package', [('itsdangerous-0.24', 'itsdangerous.py'),
                                      ('py-1.4.32', 'py/__init__.py')])
@@ -173,7 +172,6 @@ def test_convert_platform_to_others(testing_workdir, base_platform, package):
                 assert_package_paths_matches_files(package)
 
 
-@pytest.mark.serial
 @pytest.mark.skipif(on_win, reason="we create the pkg to be converted in *nix; don't run on win.")
 def test_convert_from_unix_to_win_creates_entry_points(testing_config):
     recipe_dir = os.path.join(metadata_dir, "entry_points")
@@ -211,7 +209,6 @@ def test_convert_from_unix_to_win_creates_entry_points(testing_config):
         assert 'Scripts/test-script-manual-postfix-script.py' in has_prefix_files
 
 
-@pytest.mark.serial
 @pytest.mark.parametrize('base_platform', ['linux', 'win', 'osx'])
 @pytest.mark.parametrize('package', [('anaconda-4.4.0', 'version.txt')])
 def test_convert_dependencies(testing_workdir, base_platform, package):
@@ -245,7 +242,6 @@ def test_convert_dependencies(testing_workdir, base_platform, package):
                 assert_package_paths_matches_files(package)
 
 
-@pytest.mark.serial
 @pytest.mark.parametrize('base_platform', ['linux', 'win', 'osx'])
 @pytest.mark.parametrize('package', [('anaconda-4.4.0', 'version.txt')])
 def test_convert_no_dependencies(testing_workdir, base_platform, package):
@@ -276,7 +272,6 @@ def test_convert_no_dependencies(testing_workdir, base_platform, package):
                 assert_package_paths_matches_files(package)
 
 
-@pytest.mark.serial
 @pytest.mark.parametrize('base_platform', ['linux', 'win', 'osx'])
 @pytest.mark.parametrize('package', [('anaconda-4.4.0', 'version.txt')])
 def test_skip_conversion(testing_workdir, base_platform, package, capfd):
diff --git a/tests/test_api_render.py b/tests/test_api_render.py
--- a/tests/test_api_render.py
+++ b/tests/test_api_render.py
@@ -146,7 +146,6 @@ def test_cross_recipe_with_only_build_section(testing_config):
     assert not metadata.config.build_is_host
 
 
-@pytest.mark.serial
 def test_setting_condarc_vars_with_env_var_expansion(testing_workdir):
     os.makedirs('config')
     # python won't be used - the stuff in the recipe folder will override it
diff --git a/tests/test_api_skeleton.py b/tests/test_api_skeleton.py
--- a/tests/test_api_skeleton.py
+++ b/tests/test_api_skeleton.py
@@ -28,7 +28,6 @@
                  ]
 
 
-@pytest.mark.serial
 @pytest.mark.parametrize("prefix, repo, package, version", repo_packages)
 def test_repo(prefix, repo, package, version, testing_workdir, testing_config):
     api.skeletonize(package, repo, version=version, output_dir=testing_workdir,
@@ -45,14 +44,12 @@ def test_repo(prefix, repo, package, version, testing_workdir, testing_config):
         raise
 
 
-@pytest.mark.serial
 def test_name_with_version_specified(testing_workdir, testing_config):
     api.skeletonize(packages='sympy', repo='pypi', version='0.7.5', config=testing_config)
     m = api.render('sympy/meta.yaml')[0][0]
     assert m.version() == "0.7.5"
 
 
-@pytest.mark.serial
 def test_pypi_url(testing_workdir, testing_config):
     api.skeletonize('https://pypi.python.org/packages/source/s/sympy/'
                     'sympy-0.7.5.tar.gz#md5=7de1adb49972a15a3dd975e879a2bea9',
@@ -61,7 +58,6 @@ def test_pypi_url(testing_workdir, testing_config):
     assert m.version() == "0.7.5"
 
 
-@pytest.mark.serial
 def test_pypi_with_setup_options(testing_workdir, testing_config):
     # Use photutils package below because skeleton will fail unless the setup.py is given
     # the flag --offline because of a bootstrapping a helper file that
@@ -76,7 +72,6 @@ def test_pypi_with_setup_options(testing_workdir, testing_config):
     assert '--offline' in m.meta['build']['script']
 
 
-@pytest.mark.serial
 def test_pypi_pin_numpy(testing_workdir, testing_config):
     # The package used here must have a numpy dependence for pin-numpy to have
     # any effect.
@@ -88,7 +83,6 @@ def test_pypi_pin_numpy(testing_workdir, testing_config):
         api.build('msumastro')
 
 
-@pytest.mark.serial
 def test_pypi_version_sorting(testing_workdir, testing_config):
     # The package used here must have a numpy dependence for pin-numpy to have
     # any effect.
@@ -102,13 +96,11 @@ def test_list_skeletons():
     assert set(skeletons) == set(['pypi', 'cran', 'cpan', 'luarocks', 'rpm'])
 
 
-@pytest.mark.serial
 def test_pypi_with_entry_points(testing_workdir):
     api.skeletonize('planemo', repo='pypi', python_version="2.7")
     assert os.path.isdir('planemo')
 
 
-@pytest.mark.serial
 def test_pypi_with_version_arg(testing_workdir):
     # regression test for https://github.com/conda/conda-build/issues/1442
     api.skeletonize('PrettyTable', 'pypi', version='0.7.2')
@@ -116,7 +108,6 @@ def test_pypi_with_version_arg(testing_workdir):
     assert parse_version(m.version()) == parse_version("0.7.2")
 
 
-@pytest.mark.serial
 def test_pypi_with_extra_specs(testing_workdir):
     # regression test for https://github.com/conda/conda-build/issues/1697
     api.skeletonize('bigfile', 'pypi', extra_specs=["cython", "mpi4py"], version='0.1.24')
@@ -126,7 +117,6 @@ def test_pypi_with_extra_specs(testing_workdir):
     assert any('mpi4py' in req for req in m.meta['requirements']['host'])
 
 
-@pytest.mark.serial
 def test_pypi_with_version_inconsistency(testing_workdir):
     # regression test for https://github.com/conda/conda-build/issues/189
     api.skeletonize('mpi4py_test', 'pypi', extra_specs=["mpi4py"], version='0.0.10')
@@ -134,7 +124,6 @@ def test_pypi_with_version_inconsistency(testing_workdir):
     assert parse_version(m.version()) == parse_version("0.0.10")
 
 
-@pytest.mark.serial
 def test_pypi_with_basic_environment_markers(testing_workdir):
     # regression test for https://github.com/conda/conda-build/issues/1974
     api.skeletonize('coconut', 'pypi', version='1.2.2')
@@ -157,14 +146,12 @@ def test_pypi_with_basic_environment_markers(testing_workdir):
         assert "pygments" not in run_reqs
 
 
-@pytest.mark.serial
 def test_setuptools_test_requirements(testing_workdir):
     api.skeletonize(packages='hdf5storage', repo='pypi')
     m = api.render('hdf5storage')[0][0]
     assert m.meta['test']['requires'] == ['nose >=1.0']
 
 
-@pytest.mark.serial
 def test_pypi_section_order_preserved(testing_workdir):
     """
     Test whether sections have been written in the correct order.
diff --git a/tests/test_cli.py b/tests/test_cli.py
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -186,7 +186,6 @@ def test_build_no_build_id(testing_workdir, testing_config):
     assert 'has_prefix_files_1' not in data
 
 
-@pytest.mark.serial
 def test_build_multiple_recipes(testing_metadata, testing_workdir, testing_config):
     """Test that building two recipes in one CLI call separates the build environment for each"""
     os.makedirs('recipe1')
@@ -368,7 +367,6 @@ def test_inspect_objects(testing_workdir, capfd):
         assert re.search('rpath:.*@loader_path', output)
 
 
-@pytest.mark.serial
 @pytest.mark.skipif(on_win, reason="Windows prefix length doesn't matter (yet?)")
 def test_inspect_prefix_length(testing_workdir, capfd):
     from conda_build import api
@@ -395,7 +393,6 @@ def test_inspect_prefix_length(testing_workdir, capfd):
     assert 'No packages found with binary prefixes shorter' in output
 
 
-@pytest.mark.serial
 def test_inspect_hash_input(testing_metadata, testing_workdir, capfd):
     testing_metadata.meta['requirements']['build'] = ['zlib']
     api.output_yaml(testing_metadata, 'meta.yaml')
@@ -408,7 +405,6 @@ def test_inspect_hash_input(testing_metadata, testing_workdir, capfd):
     assert 'zlib' in output
 
 
-@pytest.mark.serial
 def test_develop(testing_env):
     f = "https://pypi.io/packages/source/c/conda_version_test/conda_version_test-0.1.0-1.tar.gz"
     download(f, "conda_version_test.tar.gz")
@@ -507,7 +503,6 @@ def test_conda_py_no_period(testing_workdir, testing_metadata, monkeypatch):
     assert any('py34' in output for output in outputs)
 
 
-@pytest.mark.serial
 def test_build_skip_existing(testing_workdir, capfd, mocker):
     # build the recipe first
     empty_sections = os.path.join(metadata_dir, "empty_sections")
@@ -522,7 +517,6 @@ def test_build_skip_existing(testing_workdir, capfd, mocker):
     assert ("are already built" in output or "are already built" in error)
 
 
-@pytest.mark.serial
 def test_build_skip_existing_croot(testing_workdir, capfd):
     # build the recipe first
     empty_sections = os.path.join(metadata_dir, "empty_sections")
@@ -534,7 +528,6 @@ def test_build_skip_existing_croot(testing_workdir, capfd):
     assert "are already built" in output
 
 
-@pytest.mark.serial
 def test_package_test(testing_workdir, testing_metadata):
     """Test calling conda build -t <package file> - rather than <recipe dir>"""
     api.output_yaml(testing_metadata, 'recipe/meta.yaml')
@@ -543,7 +536,6 @@ def test_package_test(testing_workdir, testing_metadata):
     main_build.execute(args)
 
 
-@pytest.mark.serial
 def test_activate_scripts_not_included(testing_workdir):
     recipe = os.path.join(metadata_dir, '_activate_scripts_not_included')
     args = ['--no-anaconda-upload', '--croot', testing_workdir, recipe]
@@ -556,7 +548,6 @@ def test_activate_scripts_not_included(testing_workdir):
         assert not package_has_file(out, f)
 
 
-@pytest.mark.serial
 def test_relative_path_croot():
     # this tries to build a package while specifying the croot with a relative path:
     # conda-build --no-test --croot ./relative/path
@@ -570,7 +561,6 @@ def test_relative_path_croot():
     assert os.path.isfile(outputfile[0])
 
 
-@pytest.mark.serial
 def test_relative_path_test_artifact():
     # this test builds a package into (cwd)/relative/path and then calls:
     # conda-build --test ./relative/path/{platform}/{artifact}.tar.bz2
@@ -591,7 +581,6 @@ def test_relative_path_test_artifact():
     main_build.execute(args)
 
 
-@pytest.mark.serial
 def test_relative_path_test_recipe():
     # this test builds a package into (cwd)/relative/path and then calls:
     # conda-build --test --croot ./relative/path/ /abs/path/to/recipe
@@ -610,7 +599,6 @@ def test_relative_path_test_recipe():
     main_build.execute(args)
 
 
-@pytest.mark.serial
 def test_render_with_python_arg_reduces_subspace(capfd):
     recipe = os.path.join(metadata_dir, "..", "variants", "20_subspace_selection_cli")
     # build the package
diff --git a/tests/test_published_examples.py b/tests/test_published_examples.py
--- a/tests/test_published_examples.py
+++ b/tests/test_published_examples.py
@@ -9,7 +9,6 @@
 published_examples = os.path.join(os.path.dirname(metadata_dir), 'published_code')
 
 
-@pytest.mark.serial
 def test_skeleton_pypi(testing_workdir):
     """published in docs at http://conda.pydata.org/docs/build_tutorials/pkgs.html"""
     cmd = 'conda skeleton pypi click'
diff --git a/tests/test_source.py b/tests/test_source.py
--- a/tests/test_source.py
+++ b/tests/test_source.py
@@ -124,7 +124,6 @@ def test_hoist_different_name(testing_workdir):
     assert not os.path.isdir(testdir)
 
 
-@pytest.mark.serial
 def test_append_hash_to_fn(testing_metadata, caplog):
     relative_zip = 'testfn.zip'
     assert source.append_hash_to_fn(relative_zip, '123') == 'testfn_123.zip'
diff --git a/tests/test_subpackages.py b/tests/test_subpackages.py
--- a/tests/test_subpackages.py
+++ b/tests/test_subpackages.py
@@ -77,14 +77,6 @@ def test_run_exports_in_subpackage(testing_metadata):
     assert 'bzip2 1.0.*' in p2_final.meta['requirements']['run']
 
 
-# @pytest.mark.serial
-# def test_hash_includes_recipe_files(testing_workdir, testing_config):
-#     """Hash should include all files not specifically named in any output, plus the script for
-#     a given output."""
-#     recipe = os.path.join(subpackage_dir, 'script_install_files')
-#     outputs = api.build(recipe, config=testing_config)
-
-
 def test_subpackage_variant_override(testing_config):
     recipe = os.path.join(subpackage_dir, '_variant_override')
     outputs = api.build(recipe, config=testing_config)
@@ -169,7 +161,6 @@ def test_about_metadata(testing_config):
             assert info['home'] == 'http://not.a.url'
 
 
-@pytest.mark.serial
 def test_toplevel_entry_points_do_not_apply_to_subpackages(testing_config):
     recipe_dir = os.path.join(subpackage_dir, '_entry_points')
     outputs = api.build(recipe_dir, config=testing_config)
@@ -214,7 +205,6 @@ def test_subpackage_hash_inputs(testing_config):
             assert utils.package_has_file(out, 'info/recipe/build.sh')
 
 
-@pytest.mark.serial
 def test_overlapping_files(testing_config, caplog):
     recipe_dir = os.path.join(subpackage_dir, '_overlapping_files')
     utils.reset_deduplicator()
@@ -312,3 +302,9 @@ def test_merge_build_host_applies_in_outputs(testing_config):
             assert all(len(dep.split()) > 1 for dep in run_deps)
 
     api.build(recipe, config=testing_config)
+
+
+def test_activation_in_output_scripts(testing_config):
+    recipe = os.path.join(subpackage_dir, '_output_activation')
+    testing_config.activate = True
+    api.build(recipe, config=testing_config)
diff --git a/tests/test_variants.py b/tests/test_variants.py
--- a/tests/test_variants.py
+++ b/tests/test_variants.py
@@ -2,6 +2,7 @@
 import os
 import json
 import re
+import sys
 
 import pytest
 import yaml
@@ -177,7 +178,7 @@ def test_variant_input_with_zip_keys_keeps_zip_keys_list():
     assert 'zip_keys' in variant_list[0] and variant_list[0]['zip_keys']
 
 
-@pytest.mark.serial
+@pytest.mark.xfail(sys.platform=='win32', reason="console readout issues on appveyor")
 def test_ensure_valid_spec_on_run_and_test(testing_workdir, testing_config, caplog):
     recipe = os.path.join(recipe_dir, '14_variant_in_run_and_test')
     api.render(recipe, config=testing_config)