Repository: Parsl/parsl

Issue:
Remote function name is not resolved correctly | task_func_name: wrapper
**Describe the bug**
The stdout and stderr log filenames of Parsl apps should contain the function name of the corresponding app (Parsl 1.0.0 behavior). In master branch version (ec09fdc), the function name is appended as 'wrapper'. This unexpected behavior is only noticed in bash_apps. It works as expected for python_apps.

**To Reproduce**
Steps to reproduce the behavior, for e.g:
1. Setup Parsl latest (ec09fdc) with Python 3.7 on cluster
2. Run a test script with a bash_app named "my_bash_app"
3. Wait until tasks are finished
4. See generated Parsl stdout, stderr log files.
5. Check parsl.log for 'task_func_name': 'my_bash_app'. However, 'task_func_name': 'wrapper' is seen

**Expected behavior**
1. 'task_func_name': 'my_bash_app' in Parsl log
2. "my_bash_app" string contained in stdout and stderr log filenames.

**Environment**
 - OS: RHEL6.1
 - Python version: 3.7 (Anaconda 4.8.2)
 - Parsl version: ec09fdc2099e1795f41210bd1a159633b601cee3

**Distributed Environment**
- PBS Pro in NSCC's ASPIRE1 (HTEX)



Discussion:
@benclifford @annawoodard pls take a look
I bisected this with a simple test case and the breaking commit is:
```
benc@parsl-dev-3-7-4078:~/parsl/src/parsl$ git bisect good
8b981dc954f539bcbd04e986fec43e234e21bfaf is the first bad commit
commit 8b981dc954f539bcbd04e986fec43e234e21bfaf
Author: Yadu Nand Babuji <yadudoc1729@gmail.com>
Date:   Tue Aug 4 03:50:30 2020 -0500

    New serialization system to replace IPP serialization (#1806)
```

I suspect the lines that adjust use of `partial` are breaking something there. In the LSST branch of parsl I have those lines reverted because (according to the note I made at the time) they did something bad to memoization, with a TODO to investigate what is actually happening.

@yadudoc should follow up.


Resolving patch:
diff --git a/parsl/app/bash.py b/parsl/app/bash.py
--- a/parsl/app/bash.py
+++ b/parsl/app/bash.py
@@ -129,6 +129,7 @@ def __init__(self, func, data_flow_kernel=None, cache=False, executors='all', ig
         # this is done to avoid passing a function type in the args which parsl.serializer
         # doesn't support
         remote_fn = partial(update_wrapper(remote_side_bash_executor, self.func), self.func)
+        remote_fn.__name__ = self.func.__name__
         self.wrapped_remote_function = wrap_error(remote_fn)
 
     def __call__(self, *args, **kwargs):


Test patch:
diff --git a/parsl/tests/configs/local_threads.py b/parsl/tests/configs/local_threads.py
--- a/parsl/tests/configs/local_threads.py
+++ b/parsl/tests/configs/local_threads.py
@@ -1,9 +1,13 @@
 from parsl.config import Config
 from parsl.executors.threads import ThreadPoolExecutor
+from parsl.tests.utils import get_rundir
 
 
 def fresh_config():
-    return Config(executors=[ThreadPoolExecutor()])
+    return Config(
+        executors=[ThreadPoolExecutor()],
+        run_dir=get_rundir(),
+    )
 
 
 config = fresh_config()
diff --git a/parsl/tests/test_bash_apps/test_basic.py b/parsl/tests/test_bash_apps/test_basic.py
--- a/parsl/tests/test_bash_apps/test_basic.py
+++ b/parsl/tests/test_bash_apps/test_basic.py
@@ -3,6 +3,8 @@
 import pytest
 import shutil
 import time
+import random
+import re
 
 import parsl
 from parsl import File
@@ -21,7 +23,7 @@ def echo_to_file(inputs=[], outputs=[], stderr='std.err', stdout='std.out'):
 
 
 @bash_app
-def foo(x, y, z=10, stdout=None):
+def foo(x, y, z=10, stdout=None, label=None):
     return """echo {0} {1} {z}
     """.format(x, y, z=z)
 
@@ -31,34 +33,58 @@ def test_command_format_1():
     """Testing command format for BashApps
     """
 
-    stdout = os.path.abspath('std.out')
+    outdir = os.path.abspath('outputs')
+    stdout = os.path.join(outdir, 'foo-std.out')
     if os.path.exists(stdout):
         os.remove(stdout)
 
-    app_fu = foo(1, 4, stdout=stdout)
-    print("App_fu : ", app_fu)
+    foo_future = foo(1, 4, stdout=stdout)
+    print("[test_command_format_1] foo_future: ", foo_future)
     contents = None
 
-    assert app_fu.result() == 0, "BashApp exited with an error code : {0}".format(
-        app_fu.result())
+    assert foo_future.result() == 0, "BashApp exited with an error code : {0}".format(
+        foo_future.result())
 
     with open(stdout, 'r') as stdout_f:
         contents = stdout_f.read()
-        print("Contents : ", contents)
-
-    if os.path.exists('stdout_file'):
-        os.remove(stdout)
 
     assert contents == '1 4 10\n', 'Output does not match expected string "1 4 10", Got: "{0}"'.format(
         contents)
     return True
 
 
+@pytest.mark.issue363
+def test_auto_log_filename_format():
+    """Testing auto log filename format for BashApps
+    """
+    app_label = "label_test_auto_log_filename_format"
+    rand_int = random.randint(1000, 1000000000)
+
+    foo_future = foo(1, rand_int, stdout=parsl.AUTO_LOGNAME, label=app_label)
+    print("[test_auto_log_filename_format] foo_future: ", foo_future)
+    contents = None
+
+    assert foo_future.result() == 0, "BashApp exited with an error code : {0}".format(
+        foo_future.result())
+
+    log_fpath = foo_future.stdout
+    log_pattern = fr".*/task_\d+_foo_{app_label}"
+    assert re.match(log_pattern, log_fpath), 'Output file "{0}" does not match pattern "{1}"'.format(
+        log_fpath, log_pattern)
+    assert os.path.exists(log_fpath), 'Output file does not exist "{0}"'.format(log_fpath)
+    with open(log_fpath, 'r') as stdout_f:
+        contents = stdout_f.read()
+
+    assert contents == '1 {0} 10\n'.format(rand_int), \
+        'Output does not match expected string "1 {0} 10", Got: "{1}"'.format(rand_int, contents)
+    return True
+
+
 @pytest.mark.issue363
 def test_parallel_for(n=3):
     """Testing a simple parallel for loop
     """
-    outdir = os.path.abspath('outputs')
+    outdir = os.path.join(os.path.abspath('outputs'), 'test_parallel')
     if not os.path.exists(outdir):
         os.makedirs(outdir)
     else:
@@ -105,4 +131,5 @@ def test_parallel_for(n=3):
 
     x = test_parallel_for(int(args.count))
     y = test_command_format_1()
+    z = test_auto_log_filename_format()
     # raise_error(0)