Repository: iterative/dvc

Issue:
stage: translate `wdir` correctly to a POSIX path
**version**: 0.30.1

----

If you generate a stage file on Windows, the `wdir` path would be separated by backslashes, breaking compatibility with OS that uses POSIX paths, `..\..`

Change would be:
```diff
md5: ff074c8f3bd861c15cc10eea8e4c417d
outs:
    - cache: true md5: f8e0d8fc6aeee5ad6e032e0dbba036e1
      metric: false path: _data/dvc_dir/DATA_FILE.csv
-      wdir: ..\..
+      wdir: ../..
```


Discussion:

Resolving patch:
diff --git a/dvc/stage.py b/dvc/stage.py
--- a/dvc/stage.py
+++ b/dvc/stage.py
@@ -522,13 +522,15 @@ def load(repo, fname):
         return stage
 
     def dumpd(self):
+        from dvc.remote.local import RemoteLOCAL
+
         return {
             key: value
             for key, value in {
                 Stage.PARAM_MD5: self.md5,
                 Stage.PARAM_CMD: self.cmd,
-                Stage.PARAM_WDIR: os.path.relpath(
-                    self.wdir, os.path.dirname(self.path)
+                Stage.PARAM_WDIR: RemoteLOCAL.unixpath(
+                    os.path.relpath(self.wdir, os.path.dirname(self.path))
                 ),
                 Stage.PARAM_LOCKED: self.locked,
                 Stage.PARAM_DEPS: [d.dumpd() for d in self.deps],


Test patch:
diff --git a/tests/test_checkout.py b/tests/test_checkout.py
--- a/tests/test_checkout.py
+++ b/tests/test_checkout.py
@@ -284,8 +284,8 @@ def test(self):
         with open(self.file1_stage, "r") as fd:
             d = yaml.load(fd)
 
-        del (d[Stage.PARAM_OUTS][0][RemoteLOCAL.PARAM_CHECKSUM])
-        del (d[Stage.PARAM_DEPS][0][RemoteLOCAL.PARAM_CHECKSUM])
+        del d[Stage.PARAM_OUTS][0][RemoteLOCAL.PARAM_CHECKSUM]
+        del d[Stage.PARAM_DEPS][0][RemoteLOCAL.PARAM_CHECKSUM]
 
         with open(self.file1_stage, "w") as fd:
             yaml.dump(d, fd)
diff --git a/tests/test_data_cloud.py b/tests/test_data_cloud.py
--- a/tests/test_data_cloud.py
+++ b/tests/test_data_cloud.py
@@ -634,7 +634,7 @@ def _test(self):
 
         stage_file_path = stage.relpath
         content = load_stage_file(stage_file_path)
-        del (content["outs"][0]["md5"])
+        del content["outs"][0]["md5"]
         with open(stage_file_path, "w") as stage_file:
             yaml.dump(content, stage_file)
 
diff --git a/tests/test_repro.py b/tests/test_repro.py
--- a/tests/test_repro.py
+++ b/tests/test_repro.py
@@ -709,8 +709,8 @@ def test(self):
         with open(self.file1_stage, "r") as fd:
             d = yaml.load(fd)
 
-        del (d[Stage.PARAM_OUTS][0][RemoteLOCAL.PARAM_CHECKSUM])
-        del (d[Stage.PARAM_DEPS][0][RemoteLOCAL.PARAM_CHECKSUM])
+        del d[Stage.PARAM_OUTS][0][RemoteLOCAL.PARAM_CHECKSUM]
+        del d[Stage.PARAM_DEPS][0][RemoteLOCAL.PARAM_CHECKSUM]
 
         with open(self.file1_stage, "w") as fd:
             yaml.dump(d, fd)
diff --git a/tests/unit/stage.py b/tests/unit/stage.py
--- a/tests/unit/stage.py
+++ b/tests/unit/stage.py
@@ -48,3 +48,17 @@ def test_wdir_non_default_is_not_ignored(self):
             self.assertEqual(
                 stage._compute_md5(), "2ceba15e87f6848aa756502c1e6d24e9"
             )
+
+
+class TestPathConversion(TestCase):
+    def test(self):
+        import posixpath
+        import ntpath
+
+        stage = Stage(None, "path")
+
+        stage.wdir = posixpath.join("..", "..")
+        self.assertEqual(stage.dumpd()["wdir"], "../..")
+
+        stage.wdir = ntpath.join("..", "..")
+        self.assertEqual(stage.dumpd()["wdir"], "../..")