Repository: torressa/cspy (language: python)

Issue:
Bidirectional algorithm is not finding valid paths when using non-zero minimum resource values
**Describe the bug**
Bidirectional is not finding valid paths when using non-zero minimum resource values.

**To Reproduce**
``` python
from networkx import *
from cspy import BiDirectional

max_res = [10, 100]
min_res = [0, 1]
rc = [1,0]

# Example 1
# Source -> 0 -> Sink is valid, but not returned.
H = DiGraph(directed=True, n_res=2)
H.add_nodes_from([0,1,2,3,4,5,6,7])

H.add_edge(0, 3, res_cost=rc, weight=1)
H.add_edge(0, 5, res_cost=rc, weight=1)
H.add_edge(0, 'Sink', res_cost=rc, weight=1)
H.add_edge(1, 3, res_cost=rc, weight=1)
H.add_edge(1, 5, res_cost=rc, weight=1)
H.add_edge(1, 'Sink', res_cost=rc, weight=1)
H.add_edge(2, 1, res_cost=rc, weight=1)
H.add_edge(2, 4, res_cost=rc, weight=1)
H.add_edge(2, 'Sink', res_cost=rc, weight=1)
H.add_edge(3, 1, res_cost=rc, weight=1)
H.add_edge(3, 4, res_cost=rc, weight=1)
H.add_edge(3, 'Sink', res_cost=rc, weight=1)
H.add_edge(4, 0, res_cost=rc, weight=1)
H.add_edge(4, 2, res_cost=rc, weight=1)
H.add_edge(4, 'Sink', res_cost=rc, weight=1)
H.add_edge(5, 0, res_cost=rc, weight=1)
H.add_edge(5, 2, res_cost=rc, weight=1)
H.add_edge(5, 'Sink', res_cost=rc, weight=1)
H.add_edge('Source', 0, res_cost=[1,1], weight=1)
H.add_edge('Source', 1, res_cost=rc, weight=1)
H.add_edge('Source', 2, res_cost=rc, weight=1)
H.add_edge('Source', 3, res_cost=rc, weight=1)
H.add_edge('Source', 4, res_cost=rc, weight=1)
H.add_edge('Source', 5, res_cost=rc, weight=1)

bidirec = BiDirectional(H, max_res, min_res, elementary=True, direction="forward")
bidirec.run()
print(bidirec.path)


# Example 2 (subgraph of example 1)
# Source -> 0 -> Sink is valid, and is correctly returned.
H2 = DiGraph(directed=True, n_res=2)
H2.add_nodes_from([0,1,2,3,4,5,6,7])

H2.add_edge(0, 'Sink', res_cost=rc, weight=1)
H2.add_edge(1, 'Sink', res_cost=rc, weight=1)
H2.add_edge('Source', 0, res_cost=[1,1], weight=1)
H2.add_edge('Source', 1, res_cost=rc, weight=1)

bidirec = BiDirectional(H2, max_res, min_res, elementary=True, direction="forward")
bidirec.run()
print(bidirec.path)
```

**Expected behavior**
First example if failing to find Source->0->Sink path, which is valid. Using a subgraph, the second example is able to find this path.

**Desktop (please complete the following information):**
 - OS: Linux
 - Version 1.0.1

**Suggestions**
TBD

Bidirectional not finding valid path when using negative resource consumption
**Describe the bug**
The bidirectional algorithm is not finding valid path with negative resource consumption.

**To Reproduce**
```python
from cspy import BiDirectional

max_res = [6, 100]
min_res = [0, -100]

def createG():
    H = DiGraph(directed=True, n_res=2)
    H.add_nodes_from(['Source', 1, 2, 3, 4, 'Sink'])
    H.add_edge(2, 3, res_cost=[1,1], weight=1)
    H.add_edge(3, 4, res_cost=[1,1], weight=1)
    H.add_edge(4, 'Sink', res_cost=[1,1], weight=1)

    return H

# This works
H = createG()
H.add_edge('Source', 1, res_cost=[1,1], weight=1)
H.add_edge(1, 2, res_cost=[1,-1], weight=1) # -1 cost here
bidirec = BiDirectional(H, max_res, min_res, elementary=True, direction="forward")
bidirec.run()
print(bidirec.path)

# This finds no path
H = createG()
H.add_edge('Source', 1, res_cost=[1,-1], weight=1) # -1 cost here
H.add_edge(1, 2, res_cost=[1,1], weight=1)
bidirec = BiDirectional(H, max_res, min_res, elementary=True, direction="forward")
bidirec.run()
print(bidirec.path)
```

**Expected behavior**
There is one valid path from source to sink which is not found when putting a negative resource consumption at the first traversed edge. I think that this issue generally arises when the cumulative resource dips into negative values regardless of whether that is valid bad on the min_res.

**Desktop (please complete the following information):**
- OS: Linux
- Version 1.0.1 (vrpy_patch15 does not work either)

**Suggestions**
TBD

Dominance check for elementary paths
**Describe the bug**
For elementary paths, how should the dominance check be defined? Some branches have this defined a different way. In master it's currently defined as:

https://github.com/torressa/cspy/blob/1c25c1064cd492e997d053e1248caf81646f995a/src/cc/labelling.cc#L200-L213

I think patch90 is most correct, where it is defined as:
https://github.com/torressa/cspy/blob/dcea995db298c6a59bba42d7cfc792dc7fd2048f/src/cc/labelling.cc#L244-L255


**Suggestions**
The std::includes should have a negation in front of it, right?

That is, if we have two labels, L1 and L2, with unreachable node sets U1 and U2, then L1 dominates L2 iff:
- r1 <= r2 for each resource in L1, L2 respectively
- U1 is a subset of U2

In which case, should the check be the following?
```c++
  if (params_ptr->elementary && unreachable_nodes.size() > 0 &&
      other.unreachable_nodes.size() > 0) {
    if (!std::includes(
            other.unreachable_nodes.begin(),
            other.unreachable_nodes.end(),
            unreachable_nodes.begin(),
            unreachable_nodes.end())) {
      return false;
    }
  }
```
which will check if U1 is a subsequence of U2 (or equal). If U1 is not a subsequence (or equal) L1 does not dominate L2 and thus returns false. Note the difference with patch90 is changing the iterator order and removing equality check (which I believe is redundant).
Dominance check for elementary paths
**Describe the bug**
For elementary paths, how should the dominance check be defined? Some branches have this defined a different way. In master it's currently defined as:

https://github.com/torressa/cspy/blob/1c25c1064cd492e997d053e1248caf81646f995a/src/cc/labelling.cc#L200-L213

I think patch90 is most correct, where it is defined as:
https://github.com/torressa/cspy/blob/dcea995db298c6a59bba42d7cfc792dc7fd2048f/src/cc/labelling.cc#L244-L255


**Suggestions**
The std::includes should have a negation in front of it, right?

That is, if we have two labels, L1 and L2, with unreachable node sets U1 and U2, then L1 dominates L2 iff:
- r1 <= r2 for each resource in L1, L2 respectively
- U1 is a subset of U2

In which case, should the check be the following?
```c++
  if (params_ptr->elementary && unreachable_nodes.size() > 0 &&
      other.unreachable_nodes.size() > 0) {
    if (!std::includes(
            other.unreachable_nodes.begin(),
            other.unreachable_nodes.end(),
            unreachable_nodes.begin(),
            unreachable_nodes.end())) {
      return false;
    }
  }
```
which will check if U1 is a subsequence of U2 (or equal). If U1 is not a subsequence (or equal) L1 does not dominate L2 and thus returns false. Note the difference with patch90 is changing the iterator order and removing equality check (which I believe is redundant).


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/macos_cpp.yml b/.github/workflows/cpp.yml
similarity index 63%
rename from .github/workflows/macos_cpp.yml
rename to .github/workflows/cpp.yml
index 2138004..f8eaaf9 100644
--- a/.github/workflows/macos_cpp.yml
+++ b/.github/workflows/cpp.yml
@@ -1,22 +1,22 @@
-name: MacOS Cpp
+name: Cpp
 
 on: [push, pull_request]
 
 jobs:
   build:
-    runs-on: macos-latest
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [macos-11, macos-10.15, ubuntu-latest]
     steps:
     - uses: actions/checkout@v2
-    - name: Install LEMON
-      run: |
-        cp tools/docker/scripts/install_lemon .
-        chmod u+x install_lemon
-        ./install_lemon
     - name: Configure
-      run: cmake -S . -Bbuild -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Release -DBUILD_PYTHON=OFF -DBUILD_SHARED_LIBS=OFF
+      run: cmake -S . -Bbuild -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF
     - name: Build
       run: cmake --build build --config Release --target all -v
     - name: Test
       run: cd build && ctest --verbose
     - name: Install
       run: cmake --build build --config Release --target install -v -- DESTDIR=install
+      continue-on-error: true
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 0000000..75a1fc9
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,30 @@
+name: Docs
+
+on:
+  push:
+    branches: [ master ]
+
+jobs:
+
+  build_docs_job:
+    runs-on: ubuntu-latest
+    container: debian:buster-slim
+
+    steps:
+
+    - name: Prereqs
+      env:
+        GITHUB_TOKEN: ${{ secrets.GH_PAGES_TOKEN }}
+      run: |
+        apt-get update
+        apt-get install -y git
+        git clone "https://token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" ${GITHUB_REPOSITORY}
+      shell: bash
+
+    - name: Execute script to build our documentation and update pages
+      env:
+        GITHUB_TOKEN: ${{ secrets.GH_PAGES_TOKEN }}
+      run: |
+        cd ${GITHUB_REPOSITORY}
+        ./docs/build_docs.sh
+      shell: bash
diff --git a/.github/workflows/macos_dotnet.yml b/.github/workflows/dotnet.yml
similarity index 71%
rename from .github/workflows/macos_dotnet.yml
rename to .github/workflows/dotnet.yml
index 411171e..0f96bc9 100644
--- a/.github/workflows/macos_dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -1,17 +1,16 @@
-name: MacOS Dotnet
+name: Dotnet
 
 on: [push, pull_request]
 
 jobs:
   build:
-    runs-on: macos-latest
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [macos-11, macos-10.15, ubuntu-latest]
     steps:
     - uses: actions/checkout@v2
-    - name: Install LEMON
-      run: |
-        cp tools/docker/scripts/install_lemon .
-        chmod u+x install_lemon
-        ./install_lemon
     - name: Configure
       run: cmake -S . -Bbuild -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Release -DBUILD_DOTNET=ON -DBUILD_SHARED_LIBS=ON
     - name: Build
@@ -20,3 +19,4 @@ jobs:
       run: cd build && ctest --verbose
     - name: Install
       run: cmake --build build --config Release --target install -v -- DESTDIR=install
+      continue-on-error: true
diff --git a/.github/workflows/dotnet_release.yml b/.github/workflows/dotnet_release.yml
new file mode 100644
index 0000000..c365a40
--- /dev/null
+++ b/.github/workflows/dotnet_release.yml
@@ -0,0 +1,58 @@
+name: Dotnet release (nuget)
+
+on:
+  release:
+    types:
+      - published
+  push:
+    branches:
+      - master
+
+jobs:
+  unix-build:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [macos-11, macos-10.15, ubuntu-latest]
+    steps:
+    - uses: actions/checkout@v2
+    - name: Configure
+      run: cmake -S . -Bbuild -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Release -DBUILD_DOTNET=ON -DBUILD_SHARED_LIBS=ON
+    - name: Build
+      run: cmake --build build --config Release --target all -v
+    - name: Test
+      run: cd build && ctest --verbose
+    - name: Install
+      run: cmake --build build --config Release --target install -v -- DESTDIR=install
+    - name: Publish to NuGet
+      run: |
+        cd build/dotnet/packages
+        dotnet nuget push "*.nupkg" --skip-duplicate --api-key ${NUGET_API_KEY} --source https://api.nuget.org/v3/index.json
+      continue-on-error: true
+
+  windows-build:
+    runs-on: windows-latest
+    steps:
+    - uses: actions/checkout@v2
+    - name: Install SWIG 4.0.2
+      run: |
+        (New-Object System.Net.WebClient).DownloadFile("http://prdownloads.sourceforge.net/swig/swigwin-4.0.2.zip","swigwin-4.0.2.zip");
+        Expand-Archive .\swigwin-4.0.2.zip .;
+        echo "$((Get-Item .).FullName)/swigwin-4.0.2" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
+    - name: Check swig
+      run: swig -version
+    - name: Check cmake
+      run: cmake --version
+    - name: Configure
+      run: cmake -S. -Bbuild -G "Visual Studio 16 2019" -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -DBUILD_DOTNET=ON
+    - name: Build
+      run: cmake --build build --config Release --target ALL_BUILD -- /verbosity:normal /maxcpucount
+    - name: Test
+      run: cmake --build build --config Release --target RUN_TESTS -- /verbosity:normal /maxcpucount
+    - name: Install
+      run: cmake --build build --config Release --target INSTALL -- /verbosity:normal /maxcpucount
+    - name: Publish to NuGet
+      run: |
+        cd build/dotnet/packages
+        dotnet nuget push "*.nupkg" --skip-duplicate --api-key ${NUGET_API_KEY} --source https://api.nuget.org/v3/index.json
diff --git a/.github/workflows/release.yml b/.github/workflows/pypi_release.yml
similarity index 81%
rename from .github/workflows/release.yml
rename to .github/workflows/pypi_release.yml
index cfbe299..55ab95a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/pypi_release.yml
@@ -16,20 +16,13 @@ jobs:
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
-        os: [macos-latest] # 10.14 and 11 don't work
-        python-version: [3.6, 3.7, 3.8, 3.9]
+        os: [macos-10.15, macos-11] # 10.14 and 11 don't work
+        python-version: [3.6, 3.7, 3.8, 3.9, "3.10"]
     env:
       TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
       TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
     steps:
       - uses: actions/checkout@v2
-      - name: Swig install
-        run: brew install swig
-      - name: Install LEMON
-        run: |
-          cp tools/docker/scripts/install_lemon .
-          chmod u+x install_lemon
-          ./install_lemon
       - name: Set up Python ${{ matrix.python-version }}
         uses: actions/setup-python@v2
         with:
@@ -77,14 +70,6 @@ jobs:
           echo "$((Get-Item .).FullName)/swigwin-4.0.2" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
       - name: Check swig
         run: swig -version
-      - name: Install LEMON 1.3.1
-        run: |
-          (New-Object System.Net.WebClient).DownloadFile("http://lemon.cs.elte.hu/pub/sources/lemon-1.3.1.zip","lemon-1.3.1.zip");
-          Expand-Archive .\lemon-1.3.1.zip .;
-          cd lemon-1.3.1;
-          cmake -S. -Bbuild -G "Visual Studio 16 2019" -DCMAKE_BUILD_TYPE=Release -DLEMON_ENABLE_GLPK=NO -DLEMON_ENABLE_COIN=NO -DLEMON_ENABLE_ILOG=NO;
-          cmake --build build --config Release --target ALL_BUILD -- /verbosity:normal /maxcpucounte;
-          cmake --build build --config Release --target INSTALL -- /verbosity:normal /maxcpucount
       - name: Configure
         run: cmake -S. -Bbuild -G "Visual Studio 16 2019" -DCMAKE_BUILD_TYPE=Release -DBUILD_PYTHON=ON -DBUILD_TESTING=ON -DPython3_ROOT_DIR=$env:pythonLocation -DPython_ROOT_DIR=$env:pythonLocation -DPython3_FIND_STRATEGY=LOCATION -DPython3_FIND_REGISTRY=LAST
       - name: Build
diff --git a/.github/workflows/macos_python.yml b/.github/workflows/python.yml
similarity index 71%
rename from .github/workflows/macos_python.yml
rename to .github/workflows/python.yml
index 8700618..b8ca867 100644
--- a/.github/workflows/macos_python.yml
+++ b/.github/workflows/python.yml
@@ -1,22 +1,17 @@
-name: MacOS Python
+name: Python
 
 on: [push, pull_request]
 
 jobs:
   build:
-    runs-on: macos-latest
+    runs-on: ${{ matrix.os }}
     strategy:
+      fail-fast: false
       matrix:
-        python-version: [3.6, 3.7, 3.8, 3.9]
+        python-version: [3.6, 3.7, 3.8, 3.9, "3.10"]
+        os: [macos-11, macos-10.15, ubuntu-latest]
     steps:
       - uses: actions/checkout@v2
-      - name: Swig install
-        run: brew install swig
-      - name: Install LEMON
-        run: |
-          cp tools/docker/scripts/install_lemon .
-          chmod u+x install_lemon
-          ./install_lemon
       - name: Set up Python ${{ matrix.python-version }}
         uses: actions/setup-python@v2
         with:
diff --git a/.github/workflows/ubuntu_cpp.yml b/.github/workflows/ubuntu_cpp.yml
deleted file mode 100644
index 33ebbc8..0000000
--- a/.github/workflows/ubuntu_cpp.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-name: Ubuntu Cpp
-
-on: [push, pull_request]
-
-jobs:
-  build:
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - name: Install LEMON
-      run: |
-        cp tools/docker/scripts/install_lemon .
-        chmod u+x install_lemon
-        sudo ./install_lemon
-    - name: Configure
-      run: cmake -S . -Bbuild -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Release -DBUILD_PYTHON=OFF -DBUILD_SHARED_LIBS=OFF
-    - name: Build
-      run: cmake --build build --config Release --target all -v
-    - name: Test
-      run: cd build && ctest --verbose
-    - name: Install
-      run: cmake --build build --config Release --target install -v -- DESTDIR=install
-    - name: clang-format
-      uses: RafikFarhad/clang-format-github-action@v1.0.1
-      with:
-        sources: "src/**/*.h,src/**/*.cc,test/**/*.cc,test/**/*.h"
-        style: file
diff --git a/.github/workflows/ubuntu_docs.yml b/.github/workflows/ubuntu_docs.yml
deleted file mode 100644
index 0627f90..0000000
--- a/.github/workflows/ubuntu_docs.yml
+++ /dev/null
@@ -1,52 +0,0 @@
-name: Ubuntu docs
-on:
-  push:
-    branches:
-      - master
-  pull_request:
-    branches:
-  release:
-    types:
-      - published
-
-jobs:
-  notebooks:
-    name: "Upload docs"
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-
-      - name: Set up Python
-        uses: actions/setup-python@v2
-        with:
-          python-version: 3.8
-      - name: Install LEMON
-        run: |
-          cp tools/docker/scripts/install_lemon .
-          chmod u+x install_lemon
-          sudo ./install_lemon
-      - name: Configure
-        run: cmake -S . -Bbuild -DBUILD_TESTING=ON -DBUILD_PYTHON=ON -DBUILD_SHARED_LIBS=ON
-      - name: Install rtds-action
-        run: python3 -m pip install rtds-action
-      - name: Build
-        run: cmake --build build --config Release --target all -v
-      - name: Test
-        run: cd build && ctest --verbose -R python_unittest
-      - name: Install
-        run: cmake --build build --config Release --target install -v -- DESTDIR=install
-      - name: Group python package
-        run: |
-          mkdir cspy_docs
-          mv build/python/cspy/ cspy_docs/
-          mv build/python/setup.py cspy_docs/
-      - uses: actions/upload-artifact@v2
-        with:
-          name: package-for-${{ github.sha }}
-          path: cspy_docs/
-      - name: Trigger RTDs build
-        uses: dfm/rtds-action@v1
-        with:
-          webhook_url: ${{ secrets.RTDS_WEBHOOK_URL }}
-          webhook_token: ${{ secrets.RTDS_WEBHOOK_TOKEN }}
-          commit_ref: ${{ github.ref }}
diff --git a/.github/workflows/ubuntu_dotnet.yml b/.github/workflows/ubuntu_dotnet.yml
deleted file mode 100644
index 46b70bd..0000000
--- a/.github/workflows/ubuntu_dotnet.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-name: Ubuntu Dotnet
-
-on: [push, pull_request]
-
-jobs:
-  build:
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - name: Install LEMON
-      run: |
-        cp tools/docker/scripts/install_lemon .
-        chmod u+x install_lemon
-        sudo ./install_lemon
-    - name: Configure
-      run: cmake -S . -Bbuild -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Release -DBUILD_DOTNET=ON -DBUILD_SHARED_LIBS=ON
-    - name: Build
-      run: cmake --build build --config Release --target all -v
-    - name: Test
-      run: cd build && ctest --verbose
-    - name: Install
-      run: cmake --build build --config Release --target install -v -- DESTDIR=install
diff --git a/.github/workflows/ubuntu_python.yml b/.github/workflows/ubuntu_python.yml
deleted file mode 100644
index c6531b1..0000000
--- a/.github/workflows/ubuntu_python.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-name: Ubuntu Python
-
-on: [push, pull_request]
-
-jobs:
-  build:
-    runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        python-version: [3.6, 3.7, 3.8, 3.9]
-    steps:
-      - uses: actions/checkout@v2
-      - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v2
-        with:
-          python-version: ${{ matrix.python-version }}
-      - name: Install LEMON
-        run: |
-          cp tools/docker/scripts/install_lemon .
-          chmod u+x install_lemon
-          sudo ./install_lemon
-      - name: Configure
-        run: |
-          echo $pythonLocation
-          cmake -S . -Bbuild -DBUILD_TESTING=ON -DBUILD_PYTHON=ON -DBUILD_SHARED_LIBS=ON
-      - name: Build
-        run: cmake --build build --config Release --target all -v
-      - name: Run python tests
-        run: |
-          cd build/
-          ctest --verbose -R python_unittest
diff --git a/.github/workflows/windows_cpp.yml b/.github/workflows/windows_cpp.yml
index aa04fd5..10fef7c 100644
--- a/.github/workflows/windows_cpp.yml
+++ b/.github/workflows/windows_cpp.yml
@@ -13,14 +13,6 @@ jobs:
         (New-Object System.Net.WebClient).DownloadFile("http://prdownloads.sourceforge.net/swig/swigwin-4.0.2.zip","swigwin-4.0.2.zip");
         Expand-Archive .\swigwin-4.0.2.zip .;
         echo "$((Get-Item .).FullName)/swigwin-4.0.2" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
-    - name: Install LEMON 1.3.1
-      run: |
-        (New-Object System.Net.WebClient).DownloadFile("http://lemon.cs.elte.hu/pub/sources/lemon-1.3.1.zip","lemon-1.3.1.zip");
-        Expand-Archive .\lemon-1.3.1.zip .;
-        cd lemon-1.3.1;
-        cmake -S. -Bbuild -G "Visual Studio 16 2019" -DCMAKE_BUILD_TYPE=Release -DLEMON_ENABLE_GLPK=NO -DLEMON_ENABLE_COIN=NO -DLEMON_ENABLE_ILOG=NO;
-        cmake --build build --config Release --target ALL_BUILD -- /verbosity:normal /maxcpucounte;
-        cmake --build build --config Release --target INSTALL -- /verbosity:normal /maxcpucount
     - name: Check swig
       run: swig -version
     - name: Check cmake
diff --git a/.github/workflows/windows_dotnet.yml b/.github/workflows/windows_dotnet.yml
index e29c7ad..da834ff 100644
--- a/.github/workflows/windows_dotnet.yml
+++ b/.github/workflows/windows_dotnet.yml
@@ -13,14 +13,6 @@ jobs:
         (New-Object System.Net.WebClient).DownloadFile("http://prdownloads.sourceforge.net/swig/swigwin-4.0.2.zip","swigwin-4.0.2.zip");
         Expand-Archive .\swigwin-4.0.2.zip .;
         echo "$((Get-Item .).FullName)/swigwin-4.0.2" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
-    - name: Install LEMON 1.3.1
-      run: |
-        (New-Object System.Net.WebClient).DownloadFile("http://lemon.cs.elte.hu/pub/sources/lemon-1.3.1.zip","lemon-1.3.1.zip");
-        Expand-Archive .\lemon-1.3.1.zip .;
-        cd lemon-1.3.1;
-        cmake -S. -Bbuild -G "Visual Studio 16 2019" -DCMAKE_BUILD_TYPE=Release -DLEMON_ENABLE_GLPK=NO -DLEMON_ENABLE_COIN=NO -DLEMON_ENABLE_ILOG=NO;
-        cmake --build build --config Release --target ALL_BUILD -- /verbosity:normal /maxcpucounte;
-        cmake --build build --config Release --target INSTALL -- /verbosity:normal /maxcpucount
     - name: Check swig
       run: swig -version
     - name: Check cmake
diff --git a/.github/workflows/windows_python.yml b/.github/workflows/windows_python.yml
index 5cea084..9b4f2f2 100644
--- a/.github/workflows/windows_python.yml
+++ b/.github/workflows/windows_python.yml
@@ -8,7 +8,7 @@ jobs:
     runs-on: windows-latest
     strategy:
       matrix:
-        python-version: [3.6, 3.7, 3.8, 3.9]
+        python-version: [3.6, 3.7, 3.8, 3.9, "3.10"]
     steps:
     - uses: actions/checkout@v2
     - name: Set up Python ${{ matrix.python-version }}
@@ -24,14 +24,6 @@ jobs:
         echo "$((Get-Item .).FullName)/swigwin-4.0.2" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
     - name: Check swig
       run: swig -version
-    - name: Install LEMON 1.3.1
-      run: |
-        (New-Object System.Net.WebClient).DownloadFile("http://lemon.cs.elte.hu/pub/sources/lemon-1.3.1.zip","lemon-1.3.1.zip");
-        Expand-Archive .\lemon-1.3.1.zip .;
-        cd lemon-1.3.1;
-        cmake -S. -Bbuild -G "Visual Studio 16 2019" -DCMAKE_BUILD_TYPE=Release -DLEMON_ENABLE_GLPK=NO -DLEMON_ENABLE_COIN=NO -DLEMON_ENABLE_ILOG=NO;
-        cmake --build build --config Release --target ALL_BUILD -- /verbosity:normal /maxcpucounte;
-        cmake --build build --config Release --target INSTALL -- /verbosity:normal /maxcpucount
     - name: Configure
       run: cmake -S. -Bbuild -G "Visual Studio 16 2019" -DCMAKE_BUILD_TYPE=Release -DBUILD_PYTHON=ON -DBUILD_TESTING=ON -DPython3_ROOT_DIR=$env:pythonLocation -DPython_ROOT_DIR=$env:pythonLocation -DPython3_FIND_STRATEGY=LOCATION -DPython3_FIND_REGISTRY=LAST
     - name: Build
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2f1895c..01f100b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [unreleased]
 
+### Changed
+
+ - Refactored Python and C++ unittests.
+ - Added C# interface.
+
+### Added
+ - [WIP] New functionality to select monotonic resources.
+ - [WIP] Detection of monotonic resources.
+
 ## [v1.0.1]
 
 ### Changed
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3935e2d..e0304b0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -109,22 +109,18 @@ message(STATUS "Build C++ library: ${BUILD_CXX}")
 message(STATUS "Build Python: ${BUILD_PYTHON}")
 message(STATUS "Build Dotnet: ${BUILD_DOTNET}")
 
-# TODO Other languages option(BUILD_JAVA "Build Java Library" OFF)
-# option(BUILD_DOTNET "Build .NET Library" OFF) message(STATUS "Build Java:
-# ${BUILD_JAVA}") message(STATUS "Build .Net: ${BUILD_DOTNET}")
-
 include(FetchContent)
 set(FETCHCONTENT_QUIET OFF)
-
-# TODO: download and install lemon with FetchContent
-
-# FetchContent_Declare(lemon URL
-# http://lemon.cs.elte.hu/pub/sources/lemon-1.3.1.tar.gz)
-# option(LEMON_ENABLE_GLPK NO) option(LEMON_ENABLE_COIN NO)
-# option(LEMON_ENABLE_ILOG NO) option(LEMON_ENABLE_SOPLEX NO)
-# FetchContent_MakeAvailable(lemon)
-
-find_package(LEMON REQUIRED)
+set(LEMON_BUILD_TESTING OFF)
+# Remove shared libs for LEMON
+set(BUILD_SHARED_LIBS_OLD ${BUILD_SHARED_LIBS})
+set(BUILD_SHARED_LIBS OFF)
+FetchContent_Declare(
+  LEMON
+  GIT_REPOSITORY "https://github.com/MultiFlow/LEMON.git"
+  GIT_TAG "multiflow/dev")
+FetchContent_MakeAvailable(LEMON)
+set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_OLD})
 
 enable_testing()
 include(cpp)
@@ -141,7 +137,8 @@ endif()
 include(python)
 include(dotnet)
 
-if(NOT (BUILD_PYTHON OR BUILD_DOTNET))
+# Fetch googletest and add C++ tests only if required
+if(NOT (BUILD_PYTHON OR BUILD_DOTNET) AND BUILD_TESTING)
   set(FETCHCONTENT_QUIET OFF)
   # Prevent overriding the parent project's compiler/linker settings on Windows
   set(gtest_force_shared_crt
@@ -149,8 +146,8 @@ if(NOT (BUILD_PYTHON OR BUILD_DOTNET))
       CACHE BOOL "" FORCE)
   FetchContent_Declare(
     googletest
-    GIT_REPOSITORY https://github.com/google/googletest.git
-    GIT_TAG master)
+    GIT_REPOSITORY "https://github.com/google/googletest.git"
+    GIT_TAG "main")
   FetchContent_MakeAvailable(googletest)
   add_subdirectory(test/cc/)
 endif()
diff --git a/README.md b/README.md
index 981fe52..8634877 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,15 @@
 | OS     | C++ | Python | Dotnet |
 |:-------|-----|--------|--------|
-| Linux  | [![Status][cpp_linux_svg]][cpp_linux_link] | [![Status][python_linux_svg]][python_linux_link]| [![Status][dotnet_linux_svg]][dotnet_linux_link] |
-| macOS  | [![Status][cpp_osx_svg]][cpp_osx_link] | [![Status][python_osx_svg]][python_osx_link] | [![Status][dotnet_osx_svg]][dotnet_osx_link] |
+| Unix (linux + macos) | [![Status][cpp_unix_svg]][cpp_unix_link] | [![Status][python_unix_svg]][python_unix_link]| [![Status][dotnet_unix_svg]][dotnet_unix_link] |
 | Windows  | [![Status][cpp_win_svg]][cpp_win_link] | [![Status][python_win_svg]][python_win_link] |[![Status][dotnet_win_svg]][dotnet_win_link] |
 
 
-[cpp_linux_svg]: https://github.com/torressa/cspy/workflows/Ubuntu%20Cpp/badge.svg
-[cpp_linux_link]: https://github.com/torressa/cspy/actions?query=workflow%3A%22Ubuntu+Cpp%22
-[python_linux_svg]: https://github.com/torressa/cspy/workflows/Ubuntu%20Python/badge.svg
-[python_linux_link]: https://github.com/torressa/cspy/actions?query=workflow%3A%22Ubuntu+Python%22
-[dotnet_linux_svg]: https://github.com/torressa/cspy/workflows/Ubuntu%20Dotnet/badge.svg
-[dotnet_linux_link]: https://github.com/torressa/cspy/actions?query=workflow%3A%22Ubuntu+Dotnet%22
-
-[cpp_osx_svg]: https://github.com/torressa/cspy/workflows/MacOS%20Cpp/badge.svg
-[cpp_osx_link]: https://github.com/torressa/cspy/actions?query=workflow%3A%22MacOS+Cpp%22
-[python_osx_svg]: https://github.com/torressa/cspy/workflows/MacOS%20Python/badge.svg
-[python_osx_link]: https://github.com/torressa/cspy/actions?query=workflow%3A%22MacOS+Python%22
-[dotnet_osx_svg]: https://github.com/torressa/cspy/workflows/MacOS%20Dotnet/badge.svg
-[dotnet_osx_link]: https://github.com/torressa/cspy/actions?query=workflow%3A%22MacOS+Dotnet%22
+[unix_svg]: https://github.com/torressa/cspy/workflows/Cpp/badge.svg
+[unix_link]: https://github.com/torressa/cspy/actions?query=workflow%3A%22Cpp%22
+[python_unix_svg]: https://github.com/torressa/cspy/workflows/Python/badge.svg
+[python_unix_link]: https://github.com/torressa/cspy/actions?query=workflow%3A%22Python%22
+[dotnet_unix_svg]: https://github.com/torressa/cspy/workflows/Dotnet/badge.svg
+[dotnet_unix_link]: https://github.com/torressa/cspy/actions?query=workflow%3A%22Dotnet%22
 
 [cpp_win_svg]: https://github.com/torressa/cspy/workflows/Windows%20Cpp/badge.svg
 [cpp_win_link]: https://github.com/torressa/cspy/actions?query=workflow%3A%22Windows+Cpp%22
@@ -26,7 +18,6 @@
 [dotnet_win_svg]: https://github.com/torressa/cspy/workflows/Windows%20Dotnet/badge.svg
 [dotnet_win_link]: https://github.com/torressa/cspy/actions?query=workflow%3A%22Windows+Dotnet%22
 
-[![Documentation Status](https://readthedocs.org/projects/cspy/badge/?version=latest)](https://cspy.readthedocs.io/en/latest/?badge=latest)
 [![PyPI version](https://badge.fury.io/py/cspy.svg)](https://badge.fury.io/py/cspy)
 [![Codacy Badge](https://api.codacy.com/project/badge/Grade/c28f50e92dae4bcc921f1bd142370608)](https://www.codacy.com/app/torressa/cspy?utm_source=github.com&utm_medium=referral&utm_content=torressa/cspy&utm_campaign=Badge_Grade)
 [![JOSS badge](https://joss.theoj.org/papers/25eda55801a528b982d03a6a61f7730d/status.svg)](https://joss.theoj.org/papers/25eda55801a528b982d03a6a61f7730d)
@@ -36,6 +27,8 @@
 
 A collection of algorithms for the (resource) Constrained Shortest Path (CSP) problem.
 
+Documentation [here](https://torressa.github.io/cspy/).
+
 The CSP problem was popularised by [Inrich and Desaulniers (2005)](https://www.researchgate.net/publication/227142556_Shortest_Path_Problems_with_Resource_Constraints). It was initially introduced as a subproblem for the bus driver scheduling problem, and has since then widely studied in a variety of different settings including: the vehicle routing problem with time windows (VRPTW), the technician routing and scheduling problem, the capacitated arc-routing problem, on-demand transportation systems, and, airport ground movement; among others.
 
 More generally, in the applied column generation framework, particularly in the scheduling related literature, the CSP problem is commonly employed to generate columns.
@@ -56,16 +49,16 @@ Currently, the exact and metaheuristic algorithms implemented include:
 
 Please see the individual algorithms API Documentation for some toy examples and more details:
 
-- [Bidirectional and monodirectional algorithms](https://cspy.readthedocs.io/en/latest/api/cspy.BiDirectional.html)
-- [Heuristic Tabu Search](https://cspy.readthedocs.io/en/latest/api/cspy.Tabu.html)
-- [Greedy Elimination Procedure](https://cspy.readthedocs.io/en/latest/api/cspy.GreedyElim.html)
-- [GRASP](https://cspy.readthedocs.io/en/latest/api/cspy.GRASP.html)
-- [PSOLGENT](https://cspy.readthedocs.io/en/latest/api/cspy.PSOLGENT.html)
+- [Bidirectional and monodirectional algorithms](https://torressa.github.io/cspy/python_api/cspy.BiDirectional.html)
+- [Heuristic Tabu Search](https://torressa.github.io/cspy/python_api/cspy.Tabu.html)
+- [Greedy Elimination Procedure](https://torressa.github.io/cspy/python_api/cspy.GreedyElim.html)
+- [GRASP](https://torressa.github.io/cspy/python_api/cspy.GRASP.html)
+- [PSOLGENT](https://torressa.github.io/cspy/python_api/cspy.PSOLGENT.html)
 ## Getting Started
 
 ### Prerequisites
 
-Conceptual background and input formatting is discussed in the [docs](https://cspy.readthedocs.io/en/latest/how_to.html).
+Conceptual background and input formatting is discussed in the [docs](https://torressa.github.io/cspy/how_to.html).
 
 Module dependencies are:
 
diff --git a/benchmarks/README.md b/benchmarks/README.md
index d961fbb..020cfec 100644
--- a/benchmarks/README.md
+++ b/benchmarks/README.md
@@ -4,7 +4,42 @@
 
 ### Comparison against `r_c_shortest_paths`
 
-time in ms
+All times are in ms
+
+#### Unreleased
+
+Average times for 100 repetitions for each instance.
+Using boost v1.75.0.
+
+|instance|both            |fwd      |best            |boost           |
+|--------|----------------|---------|----------------|----------------|
+|1       |7.37            |7.78     |7.37            |4.11            |
+|2       |7.13            |7.5      |7.13            |4               |
+|3       |7.3             |10.77    |7.3             |3               |
+|4       |6.73            |9.06     |6.73            |3.02            |
+|5       |134.27          |123.41   |123.41          |190.24          |
+|6       |116.39          |102.99   |102.99          |116.88          |
+|7       |68.27           |135.68   |68.27           |72.29           |
+|8       |33.5            |38.15    |33.5            |20.1            |
+|9       |1               |1        |1               |1               |
+|10      |1               |1        |1               |1               |
+|11      |15.03           |14.13    |14.13           |7.36            |
+|12      |14.13           |15.13    |14.13           |7.37            |
+|13      |8.1             |8.46     |8.1             |4.81            |
+|14      |0               |0        |0               |2.12            |
+|15      |42.51           |86.72    |42.51           |62.81           |
+|16      |20.31           |25.62    |20.31           |14.99           |
+|17      |63.68           |74.26    |63.68           |41.66           |
+|18      |65.01           |95.81    |65.01           |36.95           |
+|19      |35.18           |42.95    |35.18           |15.12           |
+|20      |32.78           |38.04    |32.78           |16.79           |
+|21      |8.12            |119.79   |8.12            |104.19          |
+|22      |5               |89.55    |5               |37.78           |
+|23      |1331.33         |2947.08  |1331.33         |7280            |
+|24      |314.91          |446.65   |314.91          |466.84          |
+|average |97.46           |185.06   |96.41           |354.77          |
+
+#### v1.0.1
 
 instance | both | fwd | best | boost
 -- | -- | -- | -- | --
diff --git a/docs/build_docs.sh b/docs/build_docs.sh
new file mode 100755
index 0000000..720098c
--- /dev/null
+++ b/docs/build_docs.sh
@@ -0,0 +1,88 @@
+#!/bin/bash
+set -x
+################################################################################
+# From https://tech.michaelaltfield.net/2020/07/18/sphinx-rtd-github-pages-1/
+################################################################################
+
+###################
+# INSTALL DEPENDS #
+###################
+
+apt-get update
+apt-get -y install git rsync python3-sphinx python3-sphinx-rtd-theme \
+  python3-git python3-pip python3-virtualenv python3-setuptools doxygen
+
+#####################
+# DECLARE VARIABLES #
+#####################
+
+pwd
+ls -lah
+export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
+
+##############
+# BUILD DOCS #
+##############
+
+# Steps from RTD
+python3 -m pip install --upgrade --no-cache-dir pip setuptools
+python3 -m pip install --upgrade --no-cache-dir mock pillow \
+  commonmark recommonmark sphinx sphinx-rtd-theme readthedocs-sphinx-ext
+python3 -m pip install -r docs/requirements.txt
+cd docs
+python3 -m sphinx -T -E -b html -d _build/doctrees -D language=en . _build/html
+cd ..
+
+#######################
+# Update GitHub Pages #
+#######################
+
+git config --global user.name "${GITHUB_ACTOR}"
+git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com"
+
+docroot=`mktemp -d`
+rsync -av "docs/_build/html/" "${docroot}/"
+cp -r docs/ ${docroot}/docs/
+
+ls ${docroot}
+ls ${docroot}/docs
+
+pushd "${docroot}"
+
+# don't bother maintaining history; just generate fresh
+git init
+git remote add deploy "https://token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
+git checkout -b gh-pages
+
+# add .nojekyll to the root so that github won't 404 on content added to dirs
+# that start with an underscore (_), such as our "_content" dir..
+touch .nojekyll
+
+# TODO: fix issues with custom domain
+# Add CNAME - this is required for GitHub to know what our custom domain is
+# echo "cspy.docs" > CNAME
+
+# Add README
+cat > README.md <<EOF
+# GitHub Pages Cache
+
+Nothing to see here. The contents of this branch are essentially a cache that's not intended to be viewed on github.com.
+
+
+If you're looking to update our documentation, check the relevant development branch's 'docs/' dir.
+EOF
+
+# copy the resulting html pages built from sphinx above to our new git repo
+git add .
+
+# commit all the new files
+msg="Updating Docs for commit ${GITHUB_SHA} made on `date -d"@${SOURCE_DATE_EPOCH}" --iso-8601=seconds` from ${GITHUB_REF} by ${GITHUB_ACTOR}"
+git commit -am "${msg}"
+
+# overwrite the contents of the gh-pages branch on our github.com repo
+git push deploy gh-pages --force
+
+popd # return to main repo sandbox root
+
+# exit cleanly
+exit 0
diff --git a/docs/conf.py b/docs/conf.py
index 2ac2c1e..911c20c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -48,6 +48,7 @@
 # a list of builtin themes.
 #
 # html_theme = 'classic'
+html_theme = 'sphinx_rtd_theme'
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
diff --git a/dotnet/logo.png b/dotnet/logo.png
index 7801e3f..5dbe6b0 100644
Binary files a/dotnet/logo.png and b/dotnet/logo.png differ
diff --git a/src/cc/CMakeLists.txt b/src/cc/CMakeLists.txt
index fbac194..bf8d831 100644
--- a/src/cc/CMakeLists.txt
+++ b/src/cc/CMakeLists.txt
@@ -3,16 +3,15 @@ add_library(BiDirectionalCpp "")
 target_sources(
   BiDirectionalCpp PRIVATE bidirectional.cc digraph.cc labelling.cc
                            preprocessing.cc search.cc ref_callback.cc)
-target_include_directories(
-  BiDirectionalCpp PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
-                          ${LEMON_INCLUDE_DIR} $<INSTALL_INTERFACE:include>)
+target_include_directories(BiDirectionalCpp
+                           PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>)
 
 target_compile_features(BiDirectionalCpp PUBLIC cxx_std_17)
 
 set_target_properties(BiDirectionalCpp PROPERTIES CXX_EXTENSIONS TRUE
                                                   VERSION ${PROJECT_VERSION})
 
-target_link_libraries(BiDirectionalCpp PUBLIC lemon)
+target_link_libraries(BiDirectionalCpp PRIVATE LEMON::LEMON)
 
 add_library(${PROJECT_NAME}::BiDirectionalCpp ALIAS BiDirectionalCpp)
 
diff --git a/src/cc/bidirectional.cc b/src/cc/bidirectional.cc
index b4bce7e..bf8d140 100644
--- a/src/cc/bidirectional.cc
+++ b/src/cc/bidirectional.cc
@@ -1,8 +1,9 @@
 #include "bidirectional.h"
 
 #include <algorithm> // sort, all_of, find
+#include <limits>    // numeric_limits
 
-#include "preprocessing.h" // lowerBoundWeight, getCriticalRes, INF
+#include "preprocessing.h" // lowerBoundWeight, getCriticalRes
 
 namespace bidirectional {
 
@@ -41,7 +42,7 @@ double BiDirectional::getTotalCost() const {
 
 void BiDirectional::checkCriticalRes() const {
   const std::vector<double>& res      = best_label_->resource_consumption;
-  double                     min_diff = INF;
+  double                     min_diff = std::numeric_limits<double>::infinity();
   int                        min_r    = 0;
   for (int r = 0; r < res.size(); r++) {
     const double& diff = max_res[r] - res[r];
@@ -130,21 +131,12 @@ void BiDirectional::initSearch(const Directions& direction) {
 
 void BiDirectional::initResourceBounds() {
   max_res_curr_ = max_res;
-  // If not all lower bounds are 0, initialise variable min_res_curr to
-  // vector of 0s
-  bool zeros = std::all_of(
-      min_res.begin(), min_res.end(), [](const double& d) { return d == 0.0; });
-  if (zeros == false) {
-    std::vector<double> temp(min_res.size(), 0.0);
-    min_res_curr_ = temp;
-  } else {
-    min_res_curr_ = min_res;
-  }
+  min_res_curr_ = min_res;
 }
 
 void BiDirectional::initLabels(const Directions& direction) {
   Vertex              vertex;
-  std::vector<double> res = min_res_curr_;
+  std::vector<double> res(min_res.size(), 0.0);
   std::vector<int>    path;
   Search*             search_ptr = getSearchPtr(direction);
 
@@ -254,7 +246,6 @@ void BiDirectional::move(const Directions& direction) {
 }
 
 bool BiDirectional::terminate(const Directions& direction) {
-  // Check time elapsed (if relevant)
   Search* search_ptr = getSearchPtr(direction);
   return terminate(direction, *search_ptr->intermediate_label);
 }
@@ -270,7 +261,6 @@ bool BiDirectional::terminate(
       timediff_sec >= params_ptr_->time_limit) {
     return true;
   }
-  // Check input label
   return checkValidLabel(direction, label);
 }
 
@@ -382,8 +372,8 @@ void BiDirectional::extendCurrentLabel(const Directions& direction) {
              graph_ptr_->getLNodeFromId(current_label->vertex.lemon_id));
          a != lemon::INVALID;
          ++a) {
-      extendSingleLabel(
-          current_label.get(), direction, graph_ptr_->getAdjVertex(a, true));
+      const AdjVertex& adj_v = graph_ptr_->getAdjVertex(a, true);
+      extendSingleLabel(current_label.get(), direction, adj_v);
     }
   } else {
     // For each incoming arc to the current label
@@ -392,8 +382,8 @@ void BiDirectional::extendCurrentLabel(const Directions& direction) {
              graph_ptr_->getLNodeFromId(current_label->vertex.lemon_id));
          a != lemon::INVALID;
          ++a) {
-      extendSingleLabel(
-          current_label.get(), direction, graph_ptr_->getAdjVertex(a, false));
+      const AdjVertex& adj_v = graph_ptr_->getAdjVertex(a, false);
+      extendSingleLabel(current_label.get(), direction, adj_v);
     }
   }
 }
@@ -404,11 +394,12 @@ void BiDirectional::extendSingleLabel(
     const AdjVertex&  adj_vertex) {
   if ((params_ptr_->elementary &&
        label->unreachable_nodes.find(adj_vertex.vertex.user_id) ==
-           label->unreachable_nodes.end()) ||
+           label->unreachable_nodes.cend()) ||
       !params_ptr_->elementary) {
     // extend current label along edge
     labelling::Label new_label =
         label->extend(adj_vertex, direction, max_res_curr_, min_res_curr_);
+
     // If label non-empty, (only when the extension is resource-feasible)
     if (new_label.vertex.lemon_id != -1) {
       updateEfficientLabels(direction, new_label);
@@ -564,7 +555,7 @@ void BiDirectional::postProcessing() {
 }
 
 double BiDirectional::getUB() {
-  double UB = INF;
+  double UB = std::numeric_limits<double>::infinity();
   // Extract forward and backward best labels (one's with least weight)
   const auto& fwd_best =
       fwd_search_ptr_->best_labels[graph_ptr_->sink.lemon_id];
@@ -585,7 +576,7 @@ double BiDirectional::getUB() {
 void BiDirectional::getMinimumWeights(double* fwd_min, double* bwd_min) {
   // Forward
   // init
-  *fwd_min = INF;
+  *fwd_min = std::numeric_limits<double>::infinity();
   for (const int& n : fwd_search_ptr_->visited_vertices) {
     if (n != graph_ptr_->source.lemon_id && fwd_search_ptr_->best_labels[n] &&
         fwd_search_ptr_->best_labels[n]->weight < *fwd_min) {
@@ -593,7 +584,7 @@ void BiDirectional::getMinimumWeights(double* fwd_min, double* bwd_min) {
     }
   }
   // backward
-  *bwd_min = INF;
+  *bwd_min = std::numeric_limits<double>::infinity();
   for (const int& n : bwd_search_ptr_->visited_vertices) {
     if (n != graph_ptr_->sink.lemon_id && bwd_search_ptr_->best_labels[n] &&
         bwd_search_ptr_->best_labels[n]->weight < *bwd_min) {
diff --git a/src/cc/digraph.h b/src/cc/digraph.h
index 787c797..7bcb82e 100644
--- a/src/cc/digraph.h
+++ b/src/cc/digraph.h
@@ -17,6 +17,7 @@ namespace bidirectional {
 /// Vertex to hold lemon id in the graph and the corresponding user id (may be
 /// different)
 struct Vertex {
+ public:
   int lemon_id;
   int user_id;
 };
@@ -26,6 +27,7 @@ struct Vertex {
  * (this way it can be used both forward and backward directions)
  */
 struct AdjVertex {
+ public:
   /// Tail/head vertex
   Vertex vertex;
   /// Double with weight/cost for the arc
@@ -209,9 +211,7 @@ class DiGraph {
 
  private:
   /// int. User id corresponding to the source_node in the graph.
-  int source_id_;
-  /// int. User id corresponding to the sink node in the graph.
-  int sink_id_;
+  int source_id_, sink_id_;
 
   /**
    * Extract arc resource consumption from arc map
diff --git a/src/cc/dotnet/CMakeLists.txt b/src/cc/dotnet/CMakeLists.txt
index a54f737..fd41118 100644
--- a/src/cc/dotnet/CMakeLists.txt
+++ b/src/cc/dotnet/CMakeLists.txt
@@ -14,6 +14,6 @@ set_target_properties(
              SWIG_USE_TARGET_INCLUDE_DIRECTORIES ON
              POSITION_INDEPENDENT_CODE ON)
 
-target_link_libraries(BiDirectionalDotnet PRIVATE BiDirectionalCpp)
+target_link_libraries(BiDirectionalDotnet PRIVATE BiDirectionalCpp LEMON::LEMON)
 target_include_directories(BiDirectionalDotnet
                            PRIVATE ${PROJECT_SOURCE_DIR}/src/cc)
diff --git a/src/cc/labelling.cc b/src/cc/labelling.cc
index 1c0ac39..e84ebfa 100644
--- a/src/cc/labelling.cc
+++ b/src/cc/labelling.cc
@@ -1,6 +1,6 @@
 #include "labelling.h"
 
-#include <algorithm> // sort, includes, copy_if, find, push/make_heap, adj_vertex
+#include <algorithm> // sort, includes, copy_if, push/make_heap
 #include <iostream>  // ostream
 
 namespace labelling {
@@ -22,26 +22,12 @@ Label::Label(
       resource_consumption(resource_consumption_in),
       partial_path(partial_path_in),
       params_ptr(params_ptr_in) {
-  if (params_ptr->elementary)
+  if (params_ptr->elementary) {
     // Insert elements of partial_path
-    for (const int& p : partial_path)
-      unreachable_nodes.insert(p);
+    unreachable_nodes.insert(partial_path.cbegin(), partial_path.cend());
+  }
 };
 
-// Label::Label(
-//     const double&                weight_in,
-//     const bidirectional::Vertex& vertex_in,
-//     const std::vector<double>&   resource_consumption_in,
-//     const std::vector<int>&      partial_path_in,
-//     const bidirectional::Params& params)
-//     : Label(
-//           weight_in,
-//           vertex_in,
-//           resource_consumption_in,
-//           partial_path_in,
-//           params.elementary,
-//           params.critical_res) {}
-
 Label::Label(
     const double&                weight_in,
     const bidirectional::Vertex& vertex_in,
@@ -110,8 +96,8 @@ Label Label::extend(
       new_resources,
       new_partial_path,
       params_ptr);
-  // Check feasibility before returning
-  if (new_label.checkFeasibility(max_res, min_res)) {
+  // Check feasibility (soft=true) before returning label
+  if (new_label.checkFeasibility(max_res, min_res, true)) {
     return new_label;
   } else {
     // Update current labels unreachable_nodes
@@ -126,16 +112,33 @@ Label Label::extend(
 
 bool Label::checkFeasibility(
     const std::vector<double>& max_res,
-    const std::vector<double>& min_res) const {
+    const std::vector<double>& min_res,
+    const bool&                soft) const {
   const int& resource_size = resource_consumption.size();
+  const int& c_res         = params_ptr->critical_res;
   for (int i = 0; i < resource_size; i++) {
-    if (resource_consumption[i] <= max_res[i] &&
-        resource_consumption[i] >= min_res[i]) {
-      ;
+    // Always check maximum resources
+    if (resource_consumption[i] <= max_res[i]) {
+      // Check against minimum resources only if:
+      // 1. `i` is the index of the critical resource (as the value will carry
+      // the halfway point and should always be checked).
+      // 2. The check is not soft.
+      // 3. The check is soft and value is <= 0 (in case we have negative
+      // minimum resources).
+      if (i == c_res || !soft || (soft && min_res[i] <= 0))
+        if (resource_consumption[i] >= min_res[i]) {
+          ;
+        } else {
+          // The label is infeasible because of violating a minimum resource
+          // bound
+          return false;
+        }
     } else {
+      // The label is infeasible because of violating a maximum resource bound
       return false;
     }
   }
+  // The label is feasible
   return true;
 }
 
@@ -160,12 +163,11 @@ bool Label::checkDominance(
 
   if (weight == other.weight) {
     // Check if all resources are equal
-    bool all_res_equal = true;
-    for (int i = 0; i < resource_size; i++) {
-      if (resource_consumption[i] != other.resource_consumption[i]) {
-        all_res_equal = false;
-      }
-    }
+    bool all_res_equal = std::equal(
+        resource_consumption.cbegin(),
+        resource_consumption.cend(),
+        other.resource_consumption.cbegin(),
+        other.resource_consumption.cend());
     if (all_res_equal) {
       return false;
     }
@@ -174,8 +176,16 @@ bool Label::checkDominance(
   if (weight > other.weight) {
     return false;
   }
-  if (direction == bidirectional::BWD) {
-    // Compare monotone resources
+  if (direction == bidirectional::FWD) {
+    // Forward
+    for (int i = 0; i < resource_size; i++) {
+      if (resource_consumption[i] > other.resource_consumption[i]) {
+        return false;
+      }
+    }
+  } else {
+    // Backward
+    // Compare critical resource
     if (resource_consumption[c_res] < other.resource_consumption[c_res]) {
       return false;
     }
@@ -188,30 +198,25 @@ bool Label::checkDominance(
         }
       }
     }
-  } else { // Forward
-    for (int i = 0; i < resource_size; i++) {
-      if (resource_consumption[i] > other.resource_consumption[i]) {
-        return false;
-      }
-    }
   }
   // Check for the elementary case
   if (params_ptr->elementary && unreachable_nodes.size() > 0 &&
       other.unreachable_nodes.size() > 0) {
+    // check other.unreachable_nodes \subset unreachable_nodes (strict)
     if (std::includes(
-            unreachable_nodes.begin(),
-            unreachable_nodes.end(),
-            other.unreachable_nodes.begin(),
-            other.unreachable_nodes.end())) {
-      // If the unreachable_nodes are the same, this leads to one equivalent
-      // label to be removed
-      if (unreachable_nodes == other.unreachable_nodes) {
-        return true;
-      }
+            unreachable_nodes.cbegin(),
+            unreachable_nodes.cend(),
+            other.unreachable_nodes.cbegin(),
+            other.unreachable_nodes.cend()) &&
+        !std::equal(
+            unreachable_nodes.cbegin(),
+            unreachable_nodes.cend(),
+            other.unreachable_nodes.cbegin(),
+            other.unreachable_nodes.cend())) {
       return false;
     }
   }
-  // this dominates other
+  //  this dominates other
   return true;
 }
 
@@ -272,9 +277,11 @@ bool operator>(const Label& label1, const Label& label2) {
 std::ostream& operator<<(std::ostream& os, const Label& label) {
   const int& c_res = label.params_ptr->critical_res;
   os << "Label(node=" << label.vertex.user_id << ", weight= " << label.weight
-     << ", res[" << c_res << "]=" << label.resource_consumption[c_res]
-     << ", partial_path=[";
-  for (auto n : label.partial_path)
+     << ", res[";
+  for (const auto& r : label.resource_consumption)
+    os << r << ",";
+  os << "], partial_path=[";
+  for (const auto& n : label.partial_path)
     os << n << ",";
   os << "])\n";
   return os;
@@ -384,9 +391,9 @@ bool halfwayCheck(const Label& label, const std::vector<Label>& labels) {
 }
 
 bool mergePreCheck(
-    const labelling::Label&   fwd_label,
-    const labelling::Label&   bwd_label,
-    const std::vector<double> max_res) {
+    const labelling::Label&    fwd_label,
+    const labelling::Label&    bwd_label,
+    const std::vector<double>& max_res) {
   bool result = true;
   if (fwd_label.vertex.lemon_id == -1 || bwd_label.vertex.lemon_id == -1)
     return false;
diff --git a/src/cc/labelling.h b/src/cc/labelling.h
index 7642e65..ffad4b0 100644
--- a/src/cc/labelling.h
+++ b/src/cc/labelling.h
@@ -5,8 +5,8 @@
 #include <set>
 #include <vector>
 
-#include "digraph.h" // AdjVertex
-#include "params.h"  // Directions, Params
+#include "src/cc/digraph.h" // AdjVertex
+#include "src/cc/params.h"  // Directions, Params
 
 namespace labelling {
 
@@ -108,18 +108,24 @@ class Label {
       const bidirectional::Directions& direction) const;
 
   /**
-   * Check resource feasibility of current label i.e.
-   * min_res[i] <= resource_consumption[i] <= max_res[i]
-   * for i in 0..resource_consumption.size()
+   * Check resource feasibility of current label i.e. `min_res[i] <=
+   * resource_consumption[i] <= max_res[i]` for `i` in
+   * `0,...,resource_consumption.size()`.
+   * If "soft" check, then the lower bound is only checked if either: resource
+   * index `i` is the index of the critical resource or `min_res[i]<= 0`(See
+   * issue #90). If not "soft", then all lower bounds are checked as expected.
    *
    * @param[in] max_res, vector of double with upper bound(s) for resource
-   * consumption
+   * consumption. Checks values are <= bound
    * @param[in] min_res, vector of double with lower bound(s) for resource
-   * consumption
+   * consumption. Checks values are >= bound
+   * @param[in] soft, bool with whether the minimum resources should be checked
+   * "softly". Default is false.
    */
   bool checkFeasibility(
       const std::vector<double>& max_res,
-      const std::vector<double>& min_res) const;
+      const std::vector<double>& min_res,
+      const bool&                soft = false) const;
 
   /// Check if weight is under the input threshold.
   bool checkThreshold(const double& threshold) const;
@@ -203,9 +209,9 @@ Label processBwdLabel(
  * To be used before attempting to merge.
  */
 bool mergePreCheck(
-    const labelling::Label&   fwd_label,
-    const labelling::Label&   bwd_label,
-    const std::vector<double> max_res);
+    const labelling::Label&    fwd_label,
+    const labelling::Label&    bwd_label,
+    const std::vector<double>& max_res);
 
 /**
  * Returns the phi value.
diff --git a/src/cc/params.h b/src/cc/params.h
index f750145..76f8310 100644
--- a/src/cc/params.h
+++ b/src/cc/params.h
@@ -2,7 +2,6 @@
 #define BIDIRECTIONAL_PARAMS_H__
 
 #include <cmath> // nan
-#include <memory>
 #include <vector>
 
 #include "ref_callback.h"
@@ -12,13 +11,13 @@ namespace bidirectional {
 /// Internal enum for directions
 enum Directions {
   /// Forward
-  FWD = 0,
+  FWD,
   /// Backward
-  BWD = 1,
+  BWD,
   /// Both
-  BOTH = 2,
+  BOTH,
   /// No direction
-  NODIR = 3
+  NODIR
 };
 
 /**
@@ -29,7 +28,8 @@ class Params {
  public:
   /// Direction for search
   Directions direction = bidirectional::BOTH;
-  /// string with method to determine the next direction of search
+  /// string with method to determine the next direction of search. Options
+  /// are: unprocessed, processed and generated.
   std::string method = "unprocessed";
   /// double with time limit in seconds
   double time_limit = std::nan("na");
@@ -46,7 +46,6 @@ class Params {
   /// Resource id for the critical resource used in dominance checks and
   /// choosing the halfway point. Default is 0.
   int critical_res = 0;
-
   /// Callback to custom REF
   bidirectional::REFCallback* ref_callback = nullptr;
 
diff --git a/src/cc/preprocessing.h b/src/cc/preprocessing.h
index c8787fa..81d61e7 100644
--- a/src/cc/preprocessing.h
+++ b/src/cc/preprocessing.h
@@ -1,8 +1,6 @@
 #ifndef BIDIRECTIONAL_PREPROCESSING_H__
 #define BIDIRECTIONAL_PREPROCESSING_H__
 
-#define INF 10000.0
-
 #include "digraph.h"
 
 namespace bidirectional {
diff --git a/src/cc/python/CMakeLists.txt b/src/cc/python/CMakeLists.txt
index e422cc2..847a7e3 100644
--- a/src/cc/python/CMakeLists.txt
+++ b/src/cc/python/CMakeLists.txt
@@ -29,7 +29,7 @@ elseif(UNIX)
     pyBiDirectionalCpp PROPERTIES INSTALL_RPATH
                                   "$ORIGIN:$ORIGIN/../../${PROJECT_NAME}/.libs")
 endif()
-target_link_libraries(pyBiDirectionalCpp PRIVATE BiDirectionalCpp)
+target_link_libraries(pyBiDirectionalCpp PRIVATE BiDirectionalCpp LEMON::LEMON)
 
 # Variable PYTHON_LIBRARIES can contains keyword `optimized` which won't be
 # interpreted inside a generator expression. i.e. we can't use:
diff --git a/src/python/algorithms/bidirectional.py b/src/python/algorithms/bidirectional.py
index 722bddc..7807eeb 100644
--- a/src/python/algorithms/bidirectional.py
+++ b/src/python/algorithms/bidirectional.py
@@ -1,14 +1,13 @@
 # Wrapper for BiDirectionalCpp
-from typing import List, Optional, Union
-
-from networkx import DiGraph, convert_node_labels_to_integers
-from numpy.random import RandomState
+from typing import List, Optional, Tuple, Union
 
+from networkx import (DiGraph, convert_node_labels_to_integers,
+                      get_node_attributes)
 from cspy.preprocessing import preprocess_graph
-from cspy.checking import check, check_seed
+from cspy.checking import check
 
 # Import from the SWIG output file
-from .pyBiDirectionalCpp import BiDirectionalCpp, REFCallback, DoubleVector
+from .pyBiDirectionalCpp import (BiDirectionalCpp, REFCallback, DoubleVector)
 
 
 class BiDirectional:
@@ -113,12 +112,14 @@ def __init__(
         # Preprocess and save graph
         self.G: DiGraph = preprocess_graph(G, max_res, min_res, preprocess,
                                            REF_callback)
+        # Dictionary with graph label -> original label
+        self._original_node_labels = None
         # Vertex id with source/sink
         self._source_id: int = None
         self._sink_id: int = None
 
-        max_res_vector = _convert_list_to_double_vector(max_res)
-        min_res_vector = _convert_list_to_double_vector(min_res)
+        max_res_vector = _list_to_double_vector(max_res)
+        min_res_vector = _list_to_double_vector(min_res)
 
         # Pass graph
         self._init_graph()
@@ -170,7 +171,6 @@ def path(self):
         # format as list on return as SWIG returns "tuple"
         if len(path) <= 0:
             return None
-
         return [self.G.nodes[p]["original_label"] for p in path]
 
     @property
@@ -203,27 +203,39 @@ def _init_graph(self):
         # new node attribute "original_label"
         self.G = convert_node_labels_to_integers(
             self.G, label_attribute="original_label")
+        self._original_node_labels = get_node_attributes(
+            self.G, "original_label")
         # Save source and sink node ids (integers)
-        self._source_id = [
-            n for n in self.G.nodes()
-            if self.G.nodes[n]["original_label"] == "Source"
-        ][0]
-        self._sink_id = [
-            n for n in self.G.nodes()
-            if self.G.nodes[n]["original_label"] == "Sink"
-        ][0]
+        self._source_id = self._get_original_node_label("Source")
+        self._sink_id = self._get_original_node_label("Sink")
 
     def _load_graph(self):
         # Load nodes
         self.bidirectional_cpp.addNodes(list(self.G.nodes()))
         # Load each edge independently
         for edge in self.G.edges(data=True):
-            res_cost = _convert_list_to_double_vector(edge[2]["res_cost"])
+            res_cost = _list_to_double_vector(edge[2]["res_cost"])
             self.bidirectional_cpp.addEdge(edge[0], edge[1], edge[2]["weight"],
                                            res_cost)
 
+    def _get_original_node_label(self, node_label):
+        matching_labels = [
+            k for k, v in self._original_node_labels.items() if v == node_label
+        ]
+        if len(matching_labels) == 1:
+            return matching_labels[0]
+        else:
+            raise Exception("Node label not found")
+
+
+def _list_of_tuple_to_int_pair_vector(input_list: List[Tuple[int, int]]):
+    int_pair_vector = IntPairVector()
+    for (elem1, elem2) in input_list:
+        int_pair_vector.append(IntPair(elem1, elem2))
+    return int_pair_vector
+
 
-def _convert_list_to_double_vector(input_list: List[float]):
+def _list_to_double_vector(input_list: List[float]):
     double_vector = DoubleVector()
     for elem in input_list:
         double_vector.append(float(elem))
diff --git a/src/python/algorithms/grasp.py b/src/python/algorithms/grasp.py
index fcc15f3..635d057 100644
--- a/src/python/algorithms/grasp.py
+++ b/src/python/algorithms/grasp.py
@@ -1,10 +1,9 @@
 from time import time
-from math import factorial
+from itertools import repeat
 from logging import getLogger
 from collections import deque
-from itertools import permutations, repeat
+from typing import List, Optional
 from random import sample, randint
-from typing import List, Optional, Callable
 
 from networkx import DiGraph
 from numpy.random import choice
@@ -200,9 +199,8 @@ def _find_alternative_paths(G, path, rng=None):
             if rng:
                 tmp = np.empty(len(poss_edges), dtype='object')
                 tmp[:] = poss_edges
-                selection = rng.choice(tmp,
-                        replace=False,
-                        size=sample_size).tolist()
+                selection = rng.choice(tmp, replace=False,
+                                       size=sample_size).tolist()
             else:
                 selection = sample(deque(poss_edges), sample_size)
             # will use last value tried with given key
diff --git a/tools/dev/build b/tools/dev/build
index 3f9725b..c00e446 100755
--- a/tools/dev/build
+++ b/tools/dev/build
@@ -38,15 +38,15 @@ while getopts "bdcprs" opt; do
 	p )
 		BUILD_PYTHON=true
 		;;
-    r )
-        BUILD_PYTHON=true
-        BUILD_RELEASE=true
-        ;;
-    s )
+  r )
+    BUILD_PYTHON=true
+    BUILD_RELEASE=true
+    ;;
+  s )
 		BENCHMARK_TESTS=true
  		docker-compose up --build --remove-orphans boost-benchmark
 		exit 1
-        ;;
+    ;;
 	h )
 		usage
 		exit 1
diff --git a/tools/docker/Dockerfile b/tools/docker/Dockerfile
index f3caa9b..66decc9 100644
--- a/tools/docker/Dockerfile
+++ b/tools/docker/Dockerfile
@@ -3,10 +3,6 @@ FROM alpine:latest AS builder
 ENV PATH=/usr/local/bin:$PATH
 RUN apk add --no-cache git build-base linux-headers cmake wget
 
-# Download and install LEMON from source
-COPY tools/docker/scripts/install_lemon .
-RUN chmod +x install_lemon && ./install_lemon
-
 CMD [ "/bin/sh" ]
 
 FROM builder AS dev
@@ -22,6 +18,7 @@ ENV BUILD_DOCS ${BUILD_DOCS:-false}
 ENV BUILD_CLEAN ${BUILD_CLEAN:-false}
 ENV BUILD_RELEASE ${BUILD_RELEASE:-false}
 ENV BUILD_PYTHON ${BUILD_PYTHON:-false}
+ENV BUILD_DOTNET ${BUILD_DOTNET:-false}
 
 WORKDIR /root/
 COPY . .
@@ -41,18 +38,21 @@ ENV BUILD_DOCS ${BUILD_DOCS:-false}
 ENV BUILD_CLEAN ${BUILD_CLEAN:-false}
 ENV BUILD_RELEASE ${BUILD_RELEASE:-false}
 ENV BUILD_PYTHON ${BUILD_PYTHON:-false}
+# Boost vars (1.78 doesn't work)
+ENV BOOST_VERSION 1.75.0
+ENV BOOST_VERSION_UNDERSCORE 1_75_0
 
 # Install boost
-RUN wget -O boost_1_75_0.tar.gz \
- http://sourceforge.net/projects/boost/files/boost/1.75.0/boost_1_75_0.tar.gz/download \
-&& tar xzvf boost_1_75_0.tar.gz \
-&& cd boost_1_75_0 \
+RUN wget -O boost_${BOOST_VERSION_UNDERSCORE}.tar.gz \
+ http://sourceforge.net/projects/boost/files/boost/${BOOST_VERSION}/boost_${BOOST_VERSION_UNDERSCORE}.tar.gz/download \
+&& tar xzvf boost_${BOOST_VERSION_UNDERSCORE}.tar.gz \
+&& cd boost_${BOOST_VERSION_UNDERSCORE} \
 && ./bootstrap.sh --exec-prefix=/usr/local --without-libraries=python \
 && ./b2 threading=multi \
 && ./b2 install threading=multi \
 && cd .. \
-&& rm boost_1_75_0.tar.gz \
-&& rm -r boost_1_75_0
+&& rm boost_${BOOST_VERSION_UNDERSCORE}.tar.gz \
+&& rm -r boost_${BOOST_VERSION_UNDERSCORE}
 
 WORKDIR /root/
 COPY . .
diff --git a/tools/docker/python.Dockerfile b/tools/docker/python.Dockerfile
index 4aafed2..f5b129e 100644
--- a/tools/docker/python.Dockerfile
+++ b/tools/docker/python.Dockerfile
@@ -9,10 +9,6 @@ RUN git clone https://github.com/swig/swig.git --branch v4.0.2 \
 && cd swig && ./autogen.sh && ./configure \
 && make && make install && cd .. && rm -rf swig/
 
-# Download and install LEMON from source
-COPY tools/docker/scripts/install_lemon .
-RUN chmod +x install_lemon && ./install_lemon
-
 CMD [ "/usr/bin/bash" ]
 
 FROM builder AS dev
diff --git a/tools/docker/scripts/build_manylinux_wheels b/tools/docker/scripts/build_manylinux_wheels
index 743ff5c..09c6cc3 100644
--- a/tools/docker/scripts/build_manylinux_wheels
+++ b/tools/docker/scripts/build_manylinux_wheels
@@ -21,7 +21,7 @@ function repair_wheel {
 rm -rf /opt/python/cp35*
 
 cd build/python/
-mkdir wheelhouse
+mkdir -p wheelhouse
 
 # Build each wheel
 for PYBIN in /opt/python/*/bin; do
diff --git a/tools/docker/scripts/install_lemon b/tools/docker/scripts/install_lemon
deleted file mode 100644
index 950350e..0000000
--- a/tools/docker/scripts/install_lemon
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/sh
-
-set -e
-
-# Download LEMON
-wget http://lemon.cs.elte.hu/pub/sources/lemon-1.3.1.tar.gz
-tar xzf lemon-1.3.1.tar.gz
-
-# Install LEMON without solver interfaces as we don't need them
-cd lemon-1.3.1
-cmake -S. -Bbuild -DLEMON_ENABLE_GLPK=NO -DLEMON_ENABLE_COIN=NO \
-  -DLEMON_ENABLE_ILOG=NO -DLEMON_ENABLE_SOPLEX=NO
-cmake --build build --config Release --target all -v
-# cmake --build build --config Release --target check -v
-cmake --build build --config Release --target install -v
-cd ../ && rm -rf lemon-1.3.1 && rm lemon-1.3.1.tar.gz


Test patch:
diff --git a/test/cc/CMakeLists.txt b/test/cc/CMakeLists.txt
index 0217ca7..ccf1a02 100644
--- a/test/cc/CMakeLists.txt
+++ b/test/cc/CMakeLists.txt
@@ -24,6 +24,7 @@ else()
   add_executable(
     ${NAME}
     src/main.cc
+    src/utils.cc
     src/test_labelling.cc
     src/test_bidirectional.cc
     src/test_issue17.cc
@@ -31,7 +32,8 @@ else()
     src/test_issue38.cc
     src/test_issue41.cc
     src/test_issue52.cc
-    src/test_issue69.cc)
+    src/test_issue69.cc
+    src/test_issue90.cc)
 endif()
 
 # note: macOS is APPLE and also UNIX !
diff --git a/test/cc/src/test_benchmarks.cc b/test/cc/src/test_benchmarks.cc
index 4806d1c..aa8814c 100644
--- a/test/cc/src/test_benchmarks.cc
+++ b/test/cc/src/test_benchmarks.cc
@@ -1,12 +1,32 @@
-#include "test_benchmarks.h"
-
 #include <fstream>
 #include <sstream>
 
+#include "gtest/gtest.h"
+#include "src/cc/bidirectional.h"
 #include "utils.h" // loadMaxMinRes, skipLines, writeToFile, getElapsedTime, getBestCost
 
 namespace bidirectional {
 
+/**
+ * TestBenchmarks fixture class for unittests. Inherits from gtest.
+ */
+class TestBenchmarks : public ::testing::TestWithParam<int> {
+ public:
+  int               instance_number;
+  const int         number_repetitions = 100;
+  const std::string path_to_data =
+      "/root/benchmarks/beasley_christofides_1989/";
+  const std::string output_path = "/root/build/";
+  // const std::string path_to_data =
+  //     "/home/torressa/Documents/code/cspy/benchmarks/"
+  //     "beasley_christofides_1989/";
+  // const std::string output_path =
+  // "/home/torressa/Documents/code/cspy/build/";
+  std::unique_ptr<BiDirectional> bidirectional;
+
+  void SetUp() override { instance_number = GetParam(); }
+};
+
 /// Set critical resources by hand
 void setCriticalRes(BiDirectional* bidirectional, int instance_number) {
   if (instance_number == 5 || instance_number == 6)
@@ -95,26 +115,29 @@ TEST_P(TestBenchmarks, testForwardElementary) {
       &num_arcs,
       &num_resources,
       path_to_instance);
-  bidirectional = std::make_unique<BiDirectional>(
-      num_nodes, num_arcs, 1, num_nodes, max_res, min_res);
-  bidirectional->setDirection("forward");
-  bidirectional->setElementary(true);
-  bidirectional->setTimeLimit(time_limit);
-  loadGraph(
-      bidirectional.get(),
-      num_nodes,
-      num_arcs,
-      num_resources,
-      path_to_instance);
-  clock_t start = clock();
-  bidirectional->run();
-  auto cost = bidirectional->getTotalCost();
-  ASSERT_EQ(cost, getBestCost(path_to_data, instance_number));
+  double average_time = 0.0;
+  for (int i = 1; i < number_repetitions + 1; ++i) {
+    bidirectional = std::make_unique<BiDirectional>(
+        num_nodes, num_arcs, 1, num_nodes, max_res, min_res);
+    bidirectional->setDirection("forward");
+    bidirectional->setElementary(true);
+    loadGraph(
+        bidirectional.get(),
+        num_nodes,
+        num_arcs,
+        num_resources,
+        path_to_instance);
+    clock_t start = clock();
+    bidirectional->run();
+    const double elapsed_time = getElapsedTime(start);
+    average_time += (elapsed_time - average_time) / i;
+    auto cost = bidirectional->getTotalCost();
+    ASSERT_EQ(cost, getBestCost(path_to_data, instance_number));
+  }
   writeToFile(
       output_path,
       "results_fwd_elem.txt",
-      std::to_string(instance_number) + " " +
-          std::to_string(getElapsedTime(start)));
+      std::to_string(instance_number) + " " + std::to_string(average_time));
 }
 
 TEST_P(TestBenchmarks, testForward) {
@@ -129,25 +152,27 @@ TEST_P(TestBenchmarks, testForward) {
       &num_arcs,
       &num_resources,
       path_to_instance);
-  bidirectional = std::make_unique<BiDirectional>(
-      num_nodes, num_arcs, 1, num_nodes, max_res, min_res);
-  bidirectional->setDirection("forward");
-  bidirectional->setTimeLimit(time_limit);
-  loadGraph(
-      bidirectional.get(),
-      num_nodes,
-      num_arcs,
-      num_resources,
-      path_to_instance);
-  clock_t start = clock();
-  bidirectional->run();
-  auto cost = bidirectional->getTotalCost();
-  ASSERT_EQ(cost, getBestCost(path_to_data, instance_number));
+  double average_time = 0.0;
+  for (int i = 1; i < number_repetitions + 1; ++i) {
+    bidirectional = std::make_unique<BiDirectional>(
+        num_nodes, num_arcs, 1, num_nodes, max_res, min_res);
+    bidirectional->setDirection("forward");
+    loadGraph(
+        bidirectional.get(),
+        num_nodes,
+        num_arcs,
+        num_resources,
+        path_to_instance);
+    clock_t start = clock();
+    bidirectional->run();
+    average_time += (getElapsedTime(start) - average_time) / i;
+    auto cost = bidirectional->getTotalCost();
+    ASSERT_EQ(cost, getBestCost(path_to_data, instance_number));
+  }
   writeToFile(
       output_path,
       "results_fwd.txt",
-      std::to_string(instance_number) + " " +
-          std::to_string(getElapsedTime(start)));
+      std::to_string(instance_number) + " " + std::to_string(average_time));
 }
 
 TEST_P(TestBenchmarks, testForwardBoundsPruning) {
@@ -162,27 +187,29 @@ TEST_P(TestBenchmarks, testForwardBoundsPruning) {
       &num_arcs,
       &num_resources,
       path_to_instance);
-  bidirectional = std::make_unique<BiDirectional>(
-      num_nodes, num_arcs, 1, num_nodes, max_res, min_res);
-  bidirectional->setDirection("forward");
-  bidirectional->setTimeLimit(time_limit);
-  bidirectional->setBoundsPruning(true);
-  loadGraph(
-      bidirectional.get(),
-      num_nodes,
-      num_arcs,
-      num_resources,
-      path_to_instance);
+  double average_time = 0.0;
+  for (int i = 1; i < number_repetitions + 1; ++i) {
+    bidirectional = std::make_unique<BiDirectional>(
+        num_nodes, num_arcs, 1, num_nodes, max_res, min_res);
+    bidirectional->setDirection("forward");
+    bidirectional->setBoundsPruning(true);
+    loadGraph(
+        bidirectional.get(),
+        num_nodes,
+        num_arcs,
+        num_resources,
+        path_to_instance);
 
-  clock_t start = clock();
-  bidirectional->run();
-  auto cost = bidirectional->getTotalCost();
-  ASSERT_EQ(cost, getBestCost(path_to_data, instance_number));
+    clock_t start = clock();
+    bidirectional->run();
+    average_time += (getElapsedTime(start) - average_time) / i;
+    auto cost = bidirectional->getTotalCost();
+    ASSERT_EQ(cost, getBestCost(path_to_data, instance_number));
+  }
   writeToFile(
       output_path,
       "results_fwd_bp.txt",
-      std::to_string(instance_number) + " " +
-          std::to_string(getElapsedTime(start)));
+      std::to_string(instance_number) + " " + std::to_string(average_time));
 }
 
 /* Both */
@@ -201,30 +228,30 @@ TEST_P(TestBenchmarks, testBothElementary) {
       path_to_instance,
       false);
   // max_res[0] = std::ceil(max_res[0] / 2.0);
-  bidirectional = std::make_unique<BiDirectional>(
-      num_nodes, num_arcs, 1, num_nodes, max_res, min_res);
-  bidirectional->setElementary(true);
-  bidirectional->setTimeLimit(time_limit);
-  bidirectional->setFindCriticalRes(true);
-  // setCriticalRes(bidirectional.get(), instance_number);
-  loadGraph(
-      bidirectional.get(),
-      num_nodes,
-      num_arcs,
-      num_resources,
-      path_to_instance,
-      false);
-  clock_t start = clock();
-  bidirectional->run();
-  auto cost = bidirectional->getTotalCost();
-  auto path = bidirectional->getPath();
-
-  ASSERT_EQ(cost, getBestCost(path_to_data, instance_number));
+  double average_time = 0.0;
+  for (int i = 1; i < number_repetitions + 1; ++i) {
+    bidirectional = std::make_unique<BiDirectional>(
+        num_nodes, num_arcs, 1, num_nodes, max_res, min_res);
+    bidirectional->setElementary(true);
+    // bidirectional->setFindCriticalRes(true);
+    // setCriticalRes(bidirectional.get(), instance_number);
+    loadGraph(
+        bidirectional.get(),
+        num_nodes,
+        num_arcs,
+        num_resources,
+        path_to_instance,
+        false);
+    clock_t start = clock();
+    bidirectional->run();
+    average_time += (getElapsedTime(start) - average_time) / i;
+    auto cost = bidirectional->getTotalCost();
+    ASSERT_EQ(cost, getBestCost(path_to_data, instance_number));
+  }
   writeToFile(
       output_path,
       "results_both_elem.txt",
-      std::to_string(instance_number) + " " +
-          std::to_string(getElapsedTime(start)));
+      std::to_string(instance_number) + " " + std::to_string(average_time));
 }
 
 TEST_P(TestBenchmarks, testBoth) {
@@ -240,33 +267,30 @@ TEST_P(TestBenchmarks, testBoth) {
       &num_resources,
       path_to_instance,
       false);
-  bidirectional = std::make_unique<BiDirectional>(
-      num_nodes, num_arcs, 1, num_nodes, max_res, min_res);
-  bidirectional->setTimeLimit(time_limit);
-  // setCriticalRes(bidirectional.get(), instance_number);
-  bidirectional->setFindCriticalRes(true);
-  loadGraph(
-      bidirectional.get(),
-      num_nodes,
-      num_arcs,
-      num_resources,
-      path_to_instance,
-      false);
-  clock_t start = clock();
-  bidirectional->run();
-  bidirectional->checkCriticalRes();
-  auto cost = bidirectional->getTotalCost();
-  auto path = bidirectional->getPath();
-
-  ASSERT_EQ(cost, getBestCost(path_to_data, instance_number));
+  double average_time = 0.0;
+  for (int i = 1; i < number_repetitions + 1; ++i) {
+    bidirectional = std::make_unique<BiDirectional>(
+        num_nodes, num_arcs, 1, num_nodes, max_res, min_res);
+    loadGraph(
+        bidirectional.get(),
+        num_nodes,
+        num_arcs,
+        num_resources,
+        path_to_instance,
+        false);
+    clock_t start = clock();
+    bidirectional->run();
+    average_time += (getElapsedTime(start) - average_time) / i;
+    auto cost = bidirectional->getTotalCost();
+    ASSERT_EQ(cost, getBestCost(path_to_data, instance_number));
+  }
   writeToFile(
       output_path,
       "results_both.txt",
-      std::to_string(instance_number) + " " +
-          std::to_string(getElapsedTime(start)));
+      std::to_string(instance_number) + " " + std::to_string(average_time));
 }
 
-TEST_P(TestBenchmarks, testBothBoundsPruning) {
+TEST_P(TestBenchmarks, testBothFindCriticalRes) {
   const std::string path_to_instance =
       path_to_data + "rcsp" + std::to_string(instance_number) + ".txt";
   int                 num_nodes, num_arcs, num_resources;
@@ -279,30 +303,68 @@ TEST_P(TestBenchmarks, testBothBoundsPruning) {
       &num_resources,
       path_to_instance,
       false);
-  bidirectional = std::make_unique<BiDirectional>(
-      num_nodes, num_arcs, 1, num_nodes, max_res, min_res);
-  bidirectional->setElementary(true);
-  bidirectional->setTimeLimit(time_limit);
-  bidirectional->setBoundsPruning(true);
-  // setCriticalRes(bidirectional.get(), instance_number);
-  bidirectional->setFindCriticalRes(true);
-  loadGraph(
-      bidirectional.get(),
-      num_nodes,
-      num_arcs,
-      num_resources,
+  double average_time = 0.0;
+  for (int i = 1; i < number_repetitions + 1; ++i) {
+    bidirectional = std::make_unique<BiDirectional>(
+        num_nodes, num_arcs, 1, num_nodes, max_res, min_res);
+    bidirectional->setFindCriticalRes(true);
+    loadGraph(
+        bidirectional.get(),
+        num_nodes,
+        num_arcs,
+        num_resources,
+        path_to_instance,
+        false);
+    clock_t start = clock();
+    bidirectional->run();
+    average_time += (getElapsedTime(start) - average_time) / i;
+    auto cost = bidirectional->getTotalCost();
+    ASSERT_EQ(cost, getBestCost(path_to_data, instance_number));
+  }
+  writeToFile(
+      output_path,
+      "results_both_critical.txt",
+      std::to_string(instance_number) + " " + std::to_string(average_time));
+}
+
+TEST_P(TestBenchmarks, testBothBoundsPruning) {
+  const std::string path_to_instance =
+      path_to_data + "rcsp" + std::to_string(instance_number) + ".txt";
+  int                 num_nodes, num_arcs, num_resources;
+  std::vector<double> max_res, min_res;
+  loadMaxMinRes(
+      &max_res,
+      &min_res,
+      &num_nodes,
+      &num_arcs,
+      &num_resources,
       path_to_instance,
       false);
-  clock_t start = clock();
-  bidirectional->run();
-
-  auto cost = bidirectional->getTotalCost();
-  ASSERT_EQ(cost, getBestCost(path_to_data, instance_number));
+  double average_time = 0.0;
+  for (int i = 1; i < number_repetitions + 1; ++i) {
+    bidirectional = std::make_unique<BiDirectional>(
+        num_nodes, num_arcs, 1, num_nodes, max_res, min_res);
+    bidirectional->setElementary(true);
+    bidirectional->setBoundsPruning(true);
+    // setCriticalRes(bidirectional.get(), instance_number);
+    bidirectional->setFindCriticalRes(true);
+    loadGraph(
+        bidirectional.get(),
+        num_nodes,
+        num_arcs,
+        num_resources,
+        path_to_instance,
+        false);
+    clock_t start = clock();
+    bidirectional->run();
+    average_time += (getElapsedTime(start) - average_time) / i;
+    auto cost = bidirectional->getTotalCost();
+    ASSERT_EQ(cost, getBestCost(path_to_data, instance_number));
+  }
   writeToFile(
       output_path,
       "results_both_bp.txt",
-      std::to_string(instance_number) + " " +
-          std::to_string(getElapsedTime(start)));
+      std::to_string(instance_number) + " " + std::to_string(average_time));
 }
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/test/cc/src/test_benchmarks.h b/test/cc/src/test_benchmarks.h
deleted file mode 100644
index c6c430b..0000000
--- a/test/cc/src/test_benchmarks.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#ifndef TEST_TEST_BENCHMARKS_H__
-#define TEST_TEST_BENCHMARKS_H__
-
-#include "bidirectional.h"
-#include "gtest/gtest.h"
-
-namespace bidirectional {
-
-/**
- * TestBenchmarks fixture class for unittests. Inherits from gtest.
- */
-class TestBenchmarks : public ::testing::TestWithParam<int> {
- public:
-  int instance_number;
-  // const std::string path_to_data =
-  //     "/root/benchmarks/beasley_christofides_1989/";
-  // const std::string output_path = "/root/build/";
-  const std::string path_to_data =
-      "/home/torressa/Documents/code/cspy/benchmarks/"
-      "beasley_christofides_1989/";
-  const std::string output_path = "/home/torressa/Documents/code/cspy/build/";
-  std::unique_ptr<BiDirectional> bidirectional;
-  double                         time_limit = 30;
-  void SetUp() override { instance_number = GetParam(); }
-};
-
-} // namespace bidirectional
-
-#endif // TEST_TEST_BENCHMARKS_H__
diff --git a/test/cc/src/test_benchmarks_boost.cc b/test/cc/src/test_benchmarks_boost.cc
index 45eb042..9dd422f 100644
--- a/test/cc/src/test_benchmarks_boost.cc
+++ b/test/cc/src/test_benchmarks_boost.cc
@@ -1,17 +1,29 @@
-#include "test_benchmarks_boost.h"
-
 #include <boost/config.hpp>
 #include <boost/graph/adjacency_list.hpp>
 #include <boost/graph/r_c_shortest_paths.hpp>
 #include <fstream>
 #include <iostream> // cout
+#include <limits>   // numeric_limits
 #include <sstream>
 #include <vector>
 
+#include "gtest/gtest.h"
 #include "utils.h" // loadMaxMinRes, skipLines, writeToFile, getElapsedTime, getBestCost
 
 namespace boost {
 
+/**
+ * TestBenchmarks fixture class for unittests. Inherits from gtest.
+ */
+class TestBenchmarksBoost : public ::testing::TestWithParam<int> {
+ public:
+  int               instance_number;
+  const int         number_repetitions = 100;
+  const std::string path_to_data =
+      "/root/benchmarks/beasley_christofides_1989/";
+  void SetUp() override { instance_number = GetParam(); }
+};
+
 /**
  * Define all the necessary data structures
  * Based on:
@@ -206,50 +218,54 @@ TEST_P(TestBenchmarksBoost, test_r_c_shortest_paths) {
       &num_resources,
       path_to_instance);
 
-  MyGraph my_graph;
-  loadGraph(my_graph, num_nodes, num_arcs, num_resources, path_to_instance);
+  double average_time = 0.0;
+  for (int i = 1; i < number_repetitions + 1; ++i) {
+    MyGraph my_graph;
+    loadGraph(my_graph, num_nodes, num_arcs, num_resources, path_to_instance);
 
-  clock_t start = clock();
-  std::vector<std::vector<graph_traits<MyGraph>::edge_descriptor>>
-                                   opt_solutions;
-  std::vector<MyResourceContainer> pareto_opt;
-  // Source, sink
-  graph_traits<MyGraph>::vertex_descriptor s = 1;
-  graph_traits<MyGraph>::vertex_descriptor t = num_nodes;
+    clock_t start = clock();
+    std::vector<std::vector<graph_traits<MyGraph>::edge_descriptor>>
+                                     opt_solutions;
+    std::vector<MyResourceContainer> pareto_opt;
+    // Source, sink
+    graph_traits<MyGraph>::vertex_descriptor s = 1;
+    graph_traits<MyGraph>::vertex_descriptor t = num_nodes;
 
-  std::vector<double> zeros(num_resources, 0.0);
+    std::vector<double> zeros(num_resources, 0.0);
 
-  r_c_shortest_paths(
-      my_graph,
-      get(&MyVertex::id, my_graph),
-      get(&MyAdjVertex::id, my_graph),
-      s,
-      t,
-      opt_solutions,
-      pareto_opt,
-      MyResourceContainer(0.0, zeros, min_res, max_res),
-      MyResourceExtensionFunction(),
-      MyDominanceFunction(),
-      std::allocator<r_c_shortest_paths_label<MyGraph, MyResourceContainer>>(),
-      default_r_c_shortest_paths_visitor());
+    r_c_shortest_paths(
+        my_graph,
+        get(&MyVertex::id, my_graph),
+        get(&MyAdjVertex::id, my_graph),
+        s,
+        t,
+        opt_solutions,
+        pareto_opt,
+        MyResourceContainer(0.0, zeros, min_res, max_res),
+        MyResourceExtensionFunction(),
+        MyDominanceFunction(),
+        std::allocator<
+            r_c_shortest_paths_label<MyGraph, MyResourceContainer>>(),
+        default_r_c_shortest_paths_visitor());
 
-  double     cost               = 1000000.0;
-  const int& opt_solutions_size = opt_solutions.size();
-  for (int i = 0; i < opt_solutions_size; ++i) {
-    if (pareto_opt[i].weight < cost) {
-      cost = pareto_opt[i].weight;
+    double     cost               = std::numeric_limits<double>::infinity();
+    const int& opt_solutions_size = opt_solutions.size();
+    for (int i = 0; i < opt_solutions_size; ++i) {
+      if (pareto_opt[i].weight < cost) {
+        cost = pareto_opt[i].weight;
+      }
     }
-  }
-  if (instance_number == 14) {
-    ASSERT_EQ(opt_solutions_size, 0);
-  } else {
-    ASSERT_EQ(cost, getBestCost(path_to_data, instance_number));
+    if (instance_number == 14) {
+      ASSERT_EQ(opt_solutions_size, 0);
+    } else {
+      ASSERT_EQ(cost, getBestCost(path_to_data, instance_number));
+    }
+    average_time += (getElapsedTime(start) - average_time) / i;
   }
   writeToFile(
       "/root/build/",
       "results_boost.txt",
-      std::to_string(instance_number) + " " +
-          std::to_string(getElapsedTime(start)));
+      std::to_string(instance_number) + " " + std::to_string(average_time));
 }
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/test/cc/src/test_benchmarks_boost.h b/test/cc/src/test_benchmarks_boost.h
deleted file mode 100644
index 28692ae..0000000
--- a/test/cc/src/test_benchmarks_boost.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#ifndef TEST_TEST_BENCHMARKS_BOOST_H__
-#define TEST_TEST_BENCHMARKS_BOOST_H__
-
-#include "gtest/gtest.h"
-
-namespace boost {
-
-/**
- * TestBenchmarks fixture class for unittests. Inherits from gtest.
- */
-class TestBenchmarksBoost : public ::testing::TestWithParam<int> {
- public:
-  int               instance_number;
-  const std::string path_to_data =
-      "/root/benchmarks/beasley_christofides_1989/";
-  void SetUp() override { instance_number = GetParam(); }
-};
-
-} // namespace boost
-
-#endif // TEST_TEST_BENCHMARKS_BOOST_H__
diff --git a/test/cc/src/test_bidirectional.cc b/test/cc/src/test_bidirectional.cc
index 5c9ab45..b347093 100644
--- a/test/cc/src/test_bidirectional.cc
+++ b/test/cc/src/test_bidirectional.cc
@@ -1,156 +1,89 @@
-#include "test_bidirectional.h"
-
+#include <memory>
 #include <numeric>
+#include <tuple>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "src/cc/bidirectional.h"
+#include "test/cc/src/utils.h"
 
 namespace bidirectional {
 
-void createGraph(BiDirectional* bidirectional) {
-  bidirectional->addNodes({0, 1, 2, 3, 4});
-  // Add edges
-  bidirectional->addEdge(0, 1, -1, {1, 2});
-  bidirectional->addEdge(1, 2, -1, {1, 0.3});
-  bidirectional->addEdge(2, 3, -10, {1, 3});
-  bidirectional->addEdge(2, 4, 10, {1, 2});
-  bidirectional->addEdge(3, 4, -1, {1, 10});
-}
+class TestBiDirectional : public ::testing::Test {
+ protected:
+  std::unique_ptr<BiDirectional> bidirectional;
+  const int                      number_vertices = 5;
+  const int                      number_edges    = 5;
+  const std::vector<double>      max_res         = {4.0, 20.0};
+  const std::vector<double>      min_res         = {0.0, 0.0};
+  const std::vector<int>         final_path      = {0, 1, 2, 3, 4};
+  const std::vector<double>      final_res       = {4.0, 15.3};
+  const double                   final_cost      = -13.0;
+
+  void SetUp() override {
+    bidirectional = std::make_unique<BiDirectional>(
+        number_vertices, number_edges, 0, 4, max_res, min_res);
+    // Create graph
+    bidirectional->addNodes({0, 1, 2, 3, 4});
+    // Add edges
+    bidirectional->addEdge(0, 1, -1, {1, 2});
+    bidirectional->addEdge(1, 2, -1, {1, 0.3});
+    bidirectional->addEdge(2, 3, -10, {1, 3});
+    bidirectional->addEdge(2, 4, 10, {1, 2});
+    bidirectional->addEdge(3, 4, -1, {1, 10});
+  }
+};
 
 TEST_F(TestBiDirectional, testBoth) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 4, max_res, min_res);
-  createGraph(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestBiDirectional, testBothTimeLimit) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 4, max_res, min_res);
   bidirectional->setTimeLimit(0.001);
-  createGraph(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestBiDirectional, testBothThreshold) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 4, max_res, min_res);
   bidirectional->setThreshold(100);
-  createGraph(bidirectional.get());
-
   bidirectional->run();
 
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
   std::vector<int>    path_ = {0, 1, 2, 4};
   std::vector<double> res_  = {3.0, 4.3};
   double              cost_ = 8;
 
-  ASSERT_EQ(path, path_);
-  ASSERT_EQ(res, res_);
-  ASSERT_EQ(cost, cost_);
+  checkResult(*bidirectional, path_, res_, cost_);
 }
 
 TEST_F(TestBiDirectional, testBothProcessed) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 4, max_res, min_res);
   bidirectional->setMethod("processed");
-  createGraph(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestBiDirectional, testBothGenerated) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 4, max_res, min_res);
   bidirectional->setMethod("generated");
-  createGraph(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestBiDirectional, testBothBoundsPruning) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 4, max_res, min_res);
   bidirectional->setBoundsPruning(true);
-  createGraph(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestBiDirectional, testForward) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 4, max_res, min_res);
   bidirectional->setDirection("forward");
-  createGraph(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestBiDirectional, testBackward) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 4, max_res, min_res);
   bidirectional->setDirection("backward");
-  createGraph(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 } // namespace bidirectional
diff --git a/test/cc/src/test_bidirectional.h b/test/cc/src/test_bidirectional.h
deleted file mode 100644
index af958ac..0000000
--- a/test/cc/src/test_bidirectional.h
+++ /dev/null
@@ -1,27 +0,0 @@
-#ifndef TEST_TEST_BIDIRECTIONAL_H__
-#define TEST_TEST_BIDIRECTIONAL_H__
-
-#include <memory>
-#include <tuple>
-#include <vector>
-
-#include "bidirectional.h"
-#include "gtest/gtest.h"
-
-namespace bidirectional {
-
-class TestBiDirectional : public ::testing::Test {
- protected:
-  std::unique_ptr<BiDirectional> bidirectional;
-  const int                      number_vertices = 5;
-  const int                      number_edges    = 5;
-  const std::vector<double>      max_res         = {4.0, 20.0};
-  const std::vector<double>      min_res         = {0.0, 0.0};
-  const std::vector<int>         final_path      = {0, 1, 2, 3, 4};
-  const std::vector<double>      final_res       = {4.0, 15.3};
-  const double                   final_cost      = -13.0;
-};
-
-} // namespace bidirectional
-
-#endif // TEST_TEST_BIDIRECTIONAL_H__
diff --git a/test/cc/src/test_issue17.cc b/test/cc/src/test_issue17.cc
index b5e3117..e6abb44 100644
--- a/test/cc/src/test_issue17.cc
+++ b/test/cc/src/test_issue17.cc
@@ -1,176 +1,98 @@
-#include "test_issue17.h"
-
+#include <memory>
 #include <numeric>
+#include <tuple>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "src/cc/bidirectional.h"
+#include "test/cc/src/utils.h"
 
 namespace bidirectional {
 
-void createGraphIssue17(BiDirectional* bidirectional) {
-  bidirectional->addNodes({0, 1, 2, 3, 4, 5, 6});
-  bidirectional->addEdge(0, 1, 3, {1, 1});
-  bidirectional->addEdge(0, 2, 0, {1, 1});
-  bidirectional->addEdge(1, 2, -1, {1, 1});
-  bidirectional->addEdge(1, 4, 5, {1, 1});
-  bidirectional->addEdge(2, 3, 3, {1, 1});
-  bidirectional->addEdge(3, 1, 1, {1, 1});
-  bidirectional->addEdge(2, 5, -1, {1, 1});
-  bidirectional->addEdge(5, 6, 2, {1, 1});
-  bidirectional->addEdge(5, 4, -1, {1, 1});
-  bidirectional->addEdge(4, 2, 3, {1, 1});
-  bidirectional->addEdge(4, 6, 3, {1, 1});
-}
+class TestIssue17 : public ::testing::Test {
+ protected:
+  const bool                     elementary      = true;
+  const int                      number_vertices = 7;
+  const int                      number_edges    = 11;
+  const std::vector<double>      max_res         = {6.0, 6.0};
+  const std::vector<double>      min_res         = {0.0, 0.0};
+  std::unique_ptr<BiDirectional> bidirectional;
+  const std::vector<int>         final_path = {0, 2, 5, 6};
+  const std::vector<double>      final_res  = {3.0, 3.0};
+  const double                   final_cost = 1.0;
+
+  void SetUp() override {
+    bidirectional = std::make_unique<BiDirectional>(
+        number_vertices, number_edges, 0, 6, max_res, min_res);
+    // Create graph
+    bidirectional->addNodes({0, 1, 2, 3, 4, 5, 6});
+    bidirectional->addEdge(0, 1, 3, {1, 1});
+    bidirectional->addEdge(0, 2, 0, {1, 1});
+    bidirectional->addEdge(1, 2, -1, {1, 1});
+    bidirectional->addEdge(1, 4, 5, {1, 1});
+    bidirectional->addEdge(2, 3, 3, {1, 1});
+    bidirectional->addEdge(3, 1, 1, {1, 1});
+    bidirectional->addEdge(2, 5, -1, {1, 1});
+    bidirectional->addEdge(5, 6, 2, {1, 1});
+    bidirectional->addEdge(5, 4, -1, {1, 1});
+    bidirectional->addEdge(4, 2, 3, {1, 1});
+    bidirectional->addEdge(4, 6, 3, {1, 1});
+  }
+};
 
 TEST_F(TestIssue17, testBoth) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 6, max_res, min_res);
-  createGraphIssue17(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue17, testBothElementary) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 6, max_res, min_res);
   bidirectional->setElementary(true);
-  createGraphIssue17(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue17, testBothUnprocessed) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 6, max_res, min_res);
   bidirectional->setMethod("unprocessed");
-  createGraphIssue17(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue17, testBothProcessed) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 6, max_res, min_res);
   bidirectional->setMethod("processed");
-  createGraphIssue17(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue17, testBothGenerated) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 6, max_res, min_res);
   bidirectional->setMethod("generated");
-  createGraphIssue17(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue17, testForward) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 6, max_res, min_res);
   bidirectional->setDirection("forward");
-  createGraphIssue17(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue17, testForwardElementary) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 6, max_res, min_res);
   bidirectional->setDirection("forward");
   bidirectional->setElementary(true);
-  createGraphIssue17(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue17, testBackward) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 6, max_res, min_res);
   bidirectional->setDirection("backward");
-  createGraphIssue17(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue17, testBackwardElementary) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 6, max_res, min_res);
   bidirectional->setDirection("backward");
   bidirectional->setElementary(true);
-  createGraphIssue17(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 } // namespace bidirectional
diff --git a/test/cc/src/test_issue17.h b/test/cc/src/test_issue17.h
deleted file mode 100644
index 471762d..0000000
--- a/test/cc/src/test_issue17.h
+++ /dev/null
@@ -1,28 +0,0 @@
-#ifndef TEST_TEST_ISSUE17_H__
-#define TEST_TEST_ISSUE17_H__
-
-#include <memory>
-#include <tuple>
-#include <vector>
-
-#include "bidirectional.h"
-#include "gtest/gtest.h"
-
-namespace bidirectional {
-
-class TestIssue17 : public ::testing::Test {
- protected:
-  const bool                     elementary      = true;
-  const int                      number_vertices = 7;
-  const int                      number_edges    = 11;
-  const std::vector<double>      max_res         = {6.0, 6.0};
-  const std::vector<double>      min_res         = {0.0, 0.0};
-  std::unique_ptr<BiDirectional> bidirectional;
-  const std::vector<int>         final_path = {0, 2, 5, 6};
-  const std::vector<double>      final_res  = {3.0, 3.0};
-  const double                   final_cost = 1.0;
-};
-
-} // namespace bidirectional
-
-#endif // TEST_TEST_ISSUE17_H__
diff --git a/test/cc/src/test_issue22.cc b/test/cc/src/test_issue22.cc
index 0314eb4..c412b2b 100644
--- a/test/cc/src/test_issue22.cc
+++ b/test/cc/src/test_issue22.cc
@@ -1,35 +1,46 @@
-#include "test_issue22.h"
 
+#include <memory>
 #include <numeric>
+#include <tuple>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "src/cc/bidirectional.h"
+#include "test/cc/src/utils.h"
 
 namespace bidirectional {
 
-void createGraphIssue22(BiDirectional* bidirectional) {
-  bidirectional->addNodes({0, 1, 2, 3, 4});
-  bidirectional->addEdge(0, 1, 10, {1, 1});
-  bidirectional->addEdge(0, 2, 10, {1, 1});
-  bidirectional->addEdge(0, 3, 10, {1, 1});
-  bidirectional->addEdge(1, 4, -10, {1, 0});
-  bidirectional->addEdge(2, 4, -10, {1, 0});
-  bidirectional->addEdge(3, 4, -10, {1, 0});
-  bidirectional->addEdge(3, 2, -5, {1, 1});
-  bidirectional->addEdge(2, 1, -10, {1, 1});
-}
+class TestIssue22 : public ::testing::Test {
+ protected:
+  const bool                     elementary      = true;
+  const int                      number_vertices = 5;
+  const int                      number_edges    = 8;
+  const std::vector<double>      max_res         = {8.0, 2.0};
+  const std::vector<double>      min_res         = {0.0, 0.0};
+  std::unique_ptr<BiDirectional> bidirectional;
+  const std::vector<int>         final_path = {0, 2, 1, 4};
+  const std::vector<double>      final_res  = {3.0, 2.0};
+  const double                   final_cost = -10.0;
+
+  void SetUp() override {
+    bidirectional = std::make_unique<BiDirectional>(
+        number_vertices, number_edges, 0, 4, max_res, min_res);
+    // Create graph
+    bidirectional->addNodes({0, 1, 2, 3, 4});
+    bidirectional->addEdge(0, 1, 10, {1, 1});
+    bidirectional->addEdge(0, 2, 10, {1, 1});
+    bidirectional->addEdge(0, 3, 10, {1, 1});
+    bidirectional->addEdge(1, 4, -10, {1, 0});
+    bidirectional->addEdge(2, 4, -10, {1, 0});
+    bidirectional->addEdge(3, 4, -10, {1, 0});
+    bidirectional->addEdge(3, 2, -5, {1, 1});
+    bidirectional->addEdge(2, 1, -10, {1, 1});
+  }
+};
 
 TEST_F(TestIssue22, testBoth) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 4, max_res, min_res);
-  createGraphIssue22(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 } // namespace bidirectional
diff --git a/test/cc/src/test_issue22.h b/test/cc/src/test_issue22.h
deleted file mode 100644
index 859c7ae..0000000
--- a/test/cc/src/test_issue22.h
+++ /dev/null
@@ -1,28 +0,0 @@
-#ifndef TEST_TEST_ISSUE22_H__
-#define TEST_TEST_ISSUE22_H__
-
-#include <memory>
-#include <tuple>
-#include <vector>
-
-#include "bidirectional.h"
-#include "gtest/gtest.h"
-
-namespace bidirectional {
-
-class TestIssue22 : public ::testing::Test {
- protected:
-  const bool                     elementary      = true;
-  const int                      number_vertices = 5;
-  const int                      number_edges    = 8;
-  const std::vector<double>      max_res         = {8.0, 2.0};
-  const std::vector<double>      min_res         = {0.0, 0.0};
-  std::unique_ptr<BiDirectional> bidirectional;
-  const std::vector<int>         final_path = {0, 2, 1, 4};
-  const std::vector<double>      final_res  = {3.0, 2.0};
-  const double                   final_cost = -10.0;
-};
-
-} // namespace bidirectional
-
-#endif // TEST_TEST_ISSUE22_H__
diff --git a/test/cc/src/test_issue38.cc b/test/cc/src/test_issue38.cc
index 6971df5..e5f5b0f 100644
--- a/test/cc/src/test_issue38.cc
+++ b/test/cc/src/test_issue38.cc
@@ -1,28 +1,38 @@
-#include "test_issue38.h"
-
+#include <memory>
 #include <numeric>
+#include <tuple>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "src/cc/bidirectional.h"
+#include "test/cc/src/utils.h"
 
 namespace bidirectional {
 
-void createGraphIssue38(BiDirectional* bidirectional) {
-  bidirectional->addNodes({0, 1, 2});
-  bidirectional->addEdge(0, 1, 0.0, {1, 2});
-  bidirectional->addEdge(1, 2, 0.0, {1, 10});
-}
+class TestIssue38 : public ::testing::Test {
+ protected:
+  const bool                     elementary      = true;
+  const int                      number_vertices = 3;
+  const int                      number_edges    = 2;
+  const std::vector<double>      max_res         = {4.0, 20.0};
+  const std::vector<double>      min_res         = {0.0, 0.0};
+  std::unique_ptr<BiDirectional> bidirectional;
+  const std::vector<int>         final_path = {0, 1, 2};
+  const std::vector<double>      final_res  = {2.0, 12.0};
+  const double                   final_cost = 0.0;
 
-TEST_F(TestIssue38, testBoth) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 2, max_res, min_res);
-  createGraphIssue38(bidirectional.get());
+  void SetUp() override {
+    bidirectional = std::make_unique<BiDirectional>(
+        number_vertices, number_edges, 0, 2, max_res, min_res);
+    // Create graph
+    bidirectional->addNodes({0, 1, 2});
+    bidirectional->addEdge(0, 1, 0.0, {1, 2});
+    bidirectional->addEdge(1, 2, 0.0, {1, 10});
+  }
+};
 
+TEST_F(TestIssue38, testBoth) {
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 } // namespace bidirectional
diff --git a/test/cc/src/test_issue38.h b/test/cc/src/test_issue38.h
deleted file mode 100644
index ef2f4da..0000000
--- a/test/cc/src/test_issue38.h
+++ /dev/null
@@ -1,28 +0,0 @@
-#ifndef TEST_TEST_ISSUE38_H__
-#define TEST_TEST_ISSUE38_H__
-
-#include <memory>
-#include <tuple>
-#include <vector>
-
-#include "bidirectional.h"
-#include "gtest/gtest.h"
-
-namespace bidirectional {
-
-class TestIssue38 : public ::testing::Test {
- protected:
-  const bool                     elementary      = true;
-  const int                      number_vertices = 3;
-  const int                      number_edges    = 2;
-  const std::vector<double>      max_res         = {4.0, 20.0};
-  const std::vector<double>      min_res         = {0.0, 0.0};
-  std::unique_ptr<BiDirectional> bidirectional;
-  const std::vector<int>         final_path = {0, 1, 2};
-  const std::vector<double>      final_res  = {2.0, 12.0};
-  const double                   final_cost = 0.0;
-};
-
-} // namespace bidirectional
-
-#endif // TEST_TEST_ISSUE38_H__
diff --git a/test/cc/src/test_issue41.cc b/test/cc/src/test_issue41.cc
index 17ed4dc..ca276af 100644
--- a/test/cc/src/test_issue41.cc
+++ b/test/cc/src/test_issue41.cc
@@ -1,170 +1,95 @@
-#include "test_issue41.h"
-
 #include <iostream>
+#include <memory>
 #include <numeric>
+#include <tuple>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "src/cc/bidirectional.h"
+#include "test/cc/src/utils.h"
 
 namespace bidirectional {
 
-void createGraphIssue41(BiDirectional* bidirectional) {
-  bidirectional->addNodes({0, 1, 2, 3, 4});
-  bidirectional->addEdge(0, 1, 10.0, {1.0, 1.0});
-  bidirectional->addEdge(1, 2, 3.0, {1.0, 0.0});
-  bidirectional->addEdge(1, 3, 10.0, {1.0, 1.0});
-  bidirectional->addEdge(2, 3, 3.0, {1.0, 0.0});
-  bidirectional->addEdge(2, 4, 5.0, {1.0, 1.0});
-  bidirectional->addEdge(3, 4, 0.0, {1.0, 1.0});
-}
+class TestIssue41 : public ::testing::Test {
+ protected:
+  const int                      number_vertices = 5;
+  const int                      number_edges    = 6;
+  const std::vector<double>      max_res         = {3.0, 3.0};
+  const std::vector<double>      min_res         = {0.0, 3.0};
+  std::unique_ptr<BiDirectional> bidirectional;
+
+  // expected solution
+  const std::vector<int>    final_path = {0, 1, 3, 4};
+  const std::vector<double> final_res  = {3.0, 3.0};
+  const double              final_cost = 20.0;
+
+  void SetUp() override {
+    bidirectional = std::make_unique<BiDirectional>(
+        number_vertices, number_edges, 0, 4, max_res, min_res);
+    // Create graph
+    bidirectional->addNodes({0, 1, 2, 3, 4});
+    bidirectional->addEdge(0, 1, 10.0, {1.0, 1.0});
+    bidirectional->addEdge(1, 2, 3.0, {1.0, 0.0});
+    bidirectional->addEdge(1, 3, 10.0, {1.0, 1.0});
+    bidirectional->addEdge(2, 3, 3.0, {1.0, 0.0});
+    bidirectional->addEdge(2, 4, 5.0, {1.0, 1.0});
+    bidirectional->addEdge(3, 4, 0.0, {1.0, 1.0});
+  }
+};
 
 TEST_F(TestIssue41, testBoth) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 4, max_res, min_res);
-  createGraphIssue41(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue41, testBothElementary) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 4, max_res, min_res);
   bidirectional->setElementary(true);
-  createGraphIssue41(bidirectional.get());
-
   bidirectional->run();
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue41, testBothUnprocessed) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 4, max_res, min_res);
   bidirectional->setMethod("unprocessed");
-  createGraphIssue41(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue41, testBothProcessed) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 4, max_res, min_res);
   bidirectional->setMethod("processed");
-  createGraphIssue41(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue41, testBothGenerated) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 4, max_res, min_res);
   bidirectional->setMethod("generated");
-  createGraphIssue41(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue41, testForward) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 4, max_res, min_res);
   bidirectional->setDirection("forward");
-  createGraphIssue41(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue41, testForwardElementary) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 4, max_res, min_res);
   bidirectional->setDirection("forward");
   bidirectional->setElementary(true);
-  createGraphIssue41(bidirectional.get());
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue41, testBackward) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 4, max_res, min_res);
   bidirectional->setDirection("backward");
-  createGraphIssue41(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue41, testBackwardElementary) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, 0, 4, max_res, min_res);
   bidirectional->setDirection("backward");
   bidirectional->setElementary(true);
-  createGraphIssue41(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 } // namespace bidirectional
diff --git a/test/cc/src/test_issue41.h b/test/cc/src/test_issue41.h
deleted file mode 100644
index 386267e..0000000
--- a/test/cc/src/test_issue41.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#ifndef TEST_TEST_ISSUE41_H__
-#define TEST_TEST_ISSUE41_H__
-
-#include <memory>
-#include <tuple>
-#include <vector>
-
-#include "bidirectional.h"
-#include "gtest/gtest.h"
-
-namespace bidirectional {
-
-class TestIssue41 : public ::testing::Test {
- protected:
-  const int                      number_vertices = 5;
-  const int                      number_edges    = 6;
-  const std::vector<double>      max_res         = {3.0, 3.0};
-  const std::vector<double>      min_res         = {0.0, 3.0};
-  std::unique_ptr<BiDirectional> bidirectional;
-
-  // expected solution
-  const std::vector<int>    final_path = {0, 1, 3, 4};
-  const std::vector<double> final_res  = {3.0, 3.0};
-  const double              final_cost = 20.0;
-};
-
-} // namespace bidirectional
-
-#endif // TEST_TEST_ISSUE41_H__
diff --git a/test/cc/src/test_issue52.cc b/test/cc/src/test_issue52.cc
index 17f1fdc..3228dd8 100644
--- a/test/cc/src/test_issue52.cc
+++ b/test/cc/src/test_issue52.cc
@@ -1,168 +1,99 @@
-#include "test_issue52.h"
-
 #include <iostream>
+#include <memory>
 #include <numeric>
+#include <tuple>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "src/cc/bidirectional.h"
+#include "test/cc/src/utils.h"
 
 namespace bidirectional {
 
-void createGraphIssue52(BiDirectional* bidirectional) {
-  bidirectional->addNodes({0, 1, 2, 3, 4});
-  bidirectional->addEdge(0, 1, 0.0, {1});
-  bidirectional->addEdge(1, 2, -10.0, {1});
-  bidirectional->addEdge(2, 3, -10.0, {1});
-  bidirectional->addEdge(3, 1, -10.0, {1});
-  bidirectional->addEdge(1, 4, 0.0, {1});
-}
+class TestIssue52 : public ::testing::Test {
+ protected:
+  const int                      number_vertices = 5;
+  const int                      number_edges    = 5;
+  const int                      source_id       = 0;
+  const int                      sink_id         = 4;
+  const std::vector<double>      max_res         = {5.0};
+  const std::vector<double>      min_res         = {0.0};
+  std::unique_ptr<BiDirectional> bidirectional;
+  // Expected non-elementary solution
+  const std::vector<int>    final_path = {0, 1, 2, 3, 1, 4};
+  const std::vector<double> final_res  = {5.0};
+  const double              final_cost = -30.0;
+  // Expected Elementary solution
+  const std::vector<int>    elementary_path = {0, 1, 4};
+  const std::vector<double> elementary_res  = {2.0};
+  const double              elementary_cost = 0.0;
+
+  void SetUp() override {
+    bidirectional = std::make_unique<BiDirectional>(
+        number_vertices, number_edges, source_id, sink_id, max_res, min_res);
+    // Create graph
+    bidirectional->addNodes({0, 1, 2, 3, 4});
+    bidirectional->addEdge(0, 1, 0.0, {1});
+    bidirectional->addEdge(1, 2, -10.0, {1});
+    bidirectional->addEdge(2, 3, -10.0, {1});
+    bidirectional->addEdge(3, 1, -10.0, {1});
+    bidirectional->addEdge(1, 4, 0.0, {1});
+  }
+};
 
 TEST_F(TestIssue52, testBoth) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, source_id, sink_id, max_res, min_res);
-  createGraphIssue52(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue52, testBothElementary) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, source_id, sink_id, max_res, min_res);
   bidirectional->setElementary(true);
-  createGraphIssue52(bidirectional.get());
-
   bidirectional->run();
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, elementary_path);
-  ASSERT_EQ(res, elementary_res);
-  ASSERT_EQ(cost, elementary_cost);
+  checkResult(*bidirectional, elementary_path, elementary_res, elementary_cost);
 }
 
 TEST_F(TestIssue52, testBothUnprocessed) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, source_id, sink_id, max_res, min_res);
   bidirectional->setMethod("unprocessed");
-  createGraphIssue52(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue52, testBothProcessed) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, source_id, sink_id, max_res, min_res);
   bidirectional->setMethod("processed");
-  createGraphIssue52(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue52, testBothGenerated) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, source_id, sink_id, max_res, min_res);
   bidirectional->setMethod("generated");
-  createGraphIssue52(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue52, testForward) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, source_id, sink_id, max_res, min_res);
   bidirectional->setDirection("forward");
-  createGraphIssue52(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue52, testForwardElementary) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, source_id, sink_id, max_res, min_res);
   bidirectional->setDirection("forward");
   bidirectional->setElementary(true);
-  createGraphIssue52(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, elementary_path);
-  ASSERT_EQ(res, elementary_res);
-  ASSERT_EQ(cost, elementary_cost);
+  checkResult(*bidirectional, elementary_path, elementary_res, elementary_cost);
 }
 
 TEST_F(TestIssue52, testBackward) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, source_id, sink_id, max_res, min_res);
   bidirectional->setDirection("backward");
-  createGraphIssue52(bidirectional.get());
-
   bidirectional->run();
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue52, testBackwardElementary) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, source_id, sink_id, max_res, min_res);
   bidirectional->setDirection("backward");
   bidirectional->setElementary(true);
-  createGraphIssue52(bidirectional.get());
-
   bidirectional->run();
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, elementary_path);
-  ASSERT_EQ(res, elementary_res);
-  ASSERT_EQ(cost, elementary_cost);
+  checkResult(*bidirectional, elementary_path, elementary_res, elementary_cost);
 }
 
 } // namespace bidirectional
diff --git a/test/cc/src/test_issue52.h b/test/cc/src/test_issue52.h
deleted file mode 100644
index 06a48be..0000000
--- a/test/cc/src/test_issue52.h
+++ /dev/null
@@ -1,34 +0,0 @@
-#ifndef TEST_TEST_ISSUE52_H__
-#define TEST_TEST_ISSUE52_H__
-
-#include <memory>
-#include <tuple>
-#include <vector>
-
-#include "bidirectional.h"
-#include "gtest/gtest.h"
-
-namespace bidirectional {
-
-class TestIssue52 : public ::testing::Test {
- protected:
-  const int                      number_vertices = 5;
-  const int                      number_edges    = 5;
-  const int                      source_id       = 0;
-  const int                      sink_id         = 4;
-  const std::vector<double>      max_res         = {5.0};
-  const std::vector<double>      min_res         = {0.0};
-  std::unique_ptr<BiDirectional> bidirectional;
-  // Expected non-elementary solution
-  const std::vector<int>    final_path = {0, 1, 2, 3, 1, 4};
-  const std::vector<double> final_res  = {5.0};
-  const double              final_cost = -30.0;
-  // Expected Elementary solution
-  const std::vector<int>    elementary_path = {0, 1, 4};
-  const std::vector<double> elementary_res  = {2.0};
-  const double              elementary_cost = 0.0;
-};
-
-} // namespace bidirectional
-
-#endif // TEST_TEST_ISSUE52_H__
diff --git a/test/cc/src/test_issue69.cc b/test/cc/src/test_issue69.cc
index a554d70..cf6032b 100644
--- a/test/cc/src/test_issue69.cc
+++ b/test/cc/src/test_issue69.cc
@@ -1,67 +1,58 @@
-#include "test_issue69.h"
-
 #include <iostream>
+#include <memory>
 #include <numeric>
+#include <tuple>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "src/cc/bidirectional.h"
+#include "test/cc/src/utils.h"
 
 namespace bidirectional {
 
-void createGraphIssue69(BiDirectional* bidirectional) {
-  bidirectional->addNodes({1, 3, 0, 6, 7});
-  bidirectional->addEdge(1, 3, 3.0, {7, 13});
-  bidirectional->addEdge(3, 0, 4.0, {8, 10});
-  bidirectional->addEdge(3, 6, 7.0, {8, 3});
-  bidirectional->addEdge(3, 7, 1.0, {15, 12});
-  bidirectional->addEdge(0, 7, 7.0, {6, 3});
-  bidirectional->addEdge(6, 7, 8.0, {3, 8});
-}
+class TestIssue69 : public ::testing::Test {
+ protected:
+  const int                      number_vertices = 5;
+  const int                      number_edges    = 6;
+  const int                      source_id       = 1;
+  const int                      sink_id         = 7;
+  const std::vector<double>      max_res         = {20.0, 30.0};
+  const std::vector<double>      min_res         = {1.0, 0.0};
+  std::unique_ptr<BiDirectional> bidirectional;
+  // Expected non-elementary solution
+  const std::vector<int>    final_path = {1, 3, 6, 7};
+  const std::vector<double> final_res  = {18.0, 24.0};
+  const double              final_cost = 18.0;
+
+  void SetUp() override {
+    bidirectional = std::make_unique<BiDirectional>(
+        number_vertices, number_edges, source_id, sink_id, max_res, min_res);
+    // Create graph
+    bidirectional->addNodes({1, 3, 0, 6, 7});
+    bidirectional->addEdge(1, 3, 3.0, {7, 13});
+    bidirectional->addEdge(3, 0, 4.0, {8, 10});
+    bidirectional->addEdge(3, 6, 7.0, {8, 3});
+    bidirectional->addEdge(3, 7, 1.0, {15, 12});
+    bidirectional->addEdge(0, 7, 7.0, {6, 3});
+    bidirectional->addEdge(6, 7, 8.0, {3, 8});
+  }
+};
 
 TEST_F(TestIssue69, testBoth) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, source_id, sink_id, max_res, min_res);
-  createGraphIssue69(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue69, testForward) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, source_id, sink_id, max_res, min_res);
   bidirectional->setDirection("forward");
-  createGraphIssue69(bidirectional.get());
-
   bidirectional->run();
-
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 TEST_F(TestIssue69, testBackward) {
-  bidirectional = std::make_unique<BiDirectional>(
-      number_vertices, number_edges, source_id, sink_id, max_res, min_res);
   bidirectional->setDirection("backward");
-  createGraphIssue69(bidirectional.get());
-
   bidirectional->run();
-  auto path = bidirectional->getPath();
-  auto res  = bidirectional->getConsumedResources();
-  auto cost = bidirectional->getTotalCost();
-
-  ASSERT_EQ(path, final_path);
-  ASSERT_EQ(res, final_res);
-  ASSERT_EQ(cost, final_cost);
+  checkResult(*bidirectional, final_path, final_res, final_cost);
 }
 
 } // namespace bidirectional
diff --git a/test/cc/src/test_issue69.h b/test/cc/src/test_issue69.h
deleted file mode 100644
index 0b0d911..0000000
--- a/test/cc/src/test_issue69.h
+++ /dev/null
@@ -1,30 +0,0 @@
-#ifndef TEST_TEST_ISSUE69_H__
-#define TEST_TEST_ISSUE69_H__
-
-#include <memory>
-#include <tuple>
-#include <vector>
-
-#include "bidirectional.h"
-#include "gtest/gtest.h"
-
-namespace bidirectional {
-
-class TestIssue69 : public ::testing::Test {
- protected:
-  const int                      number_vertices = 5;
-  const int                      number_edges    = 6;
-  const int                      source_id       = 1;
-  const int                      sink_id         = 7;
-  const std::vector<double>      max_res         = {20.0, 30.0};
-  const std::vector<double>      min_res         = {1.0, 0.0};
-  std::unique_ptr<BiDirectional> bidirectional;
-  // Expected non-elementary solution
-  const std::vector<int>    final_path = {1, 3, 6, 7};
-  const std::vector<double> final_res  = {18.0, 24.0};
-  const double              final_cost = 18.0;
-};
-
-} // namespace bidirectional
-
-#endif // TEST_TEST_ISSUE69_H__
diff --git a/test/cc/src/test_issue90.cc b/test/cc/src/test_issue90.cc
new file mode 100644
index 0000000..c337247
--- /dev/null
+++ b/test/cc/src/test_issue90.cc
@@ -0,0 +1,57 @@
+#include <iostream>
+#include <memory>
+#include <numeric>
+#include <tuple>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "src/cc/bidirectional.h"
+#include "test/cc/src/utils.h"
+
+namespace bidirectional {
+
+class TestIssue90 : public ::testing::Test {
+ protected:
+  const int                      number_vertices = 6;
+  const int                      number_edges    = 5;
+  const int                      source_id       = 0;
+  const int                      sink_id         = 5;
+  const std::vector<double>      max_res         = {6.0, 100.0};
+  const std::vector<double>      min_res         = {0.0, -100.0};
+  std::unique_ptr<BiDirectional> bidirectional;
+  // Expected non-elementary solution
+  const std::vector<int>    final_path = {0, 1, 2, 3, 4, 5};
+  const std::vector<double> final_res  = {5.0, 3.0};
+  const double              final_cost = 5.0;
+
+  void SetUp() override {
+    bidirectional = std::make_unique<BiDirectional>(
+        number_vertices, number_edges, source_id, sink_id, max_res, min_res);
+    // Create graph
+    bidirectional->addNodes({0, 1, 2, 3, 4, 5});
+    bidirectional->addEdge(0, 1, 1.0, {1, -1});
+    bidirectional->addEdge(1, 2, 1.0, {1, 1});
+    bidirectional->addEdge(2, 3, 1.0, {1, 1});
+    bidirectional->addEdge(3, 4, 1.0, {1, 1});
+    bidirectional->addEdge(4, 5, 1.0, {1, 1});
+  }
+};
+
+TEST_F(TestIssue90, testBoth) {
+  bidirectional->run();
+  checkResult(*bidirectional, final_path, final_res, final_cost);
+}
+
+TEST_F(TestIssue90, testForward) {
+  bidirectional->setDirection("forward");
+  bidirectional->run();
+  checkResult(*bidirectional, final_path, final_res, final_cost);
+}
+
+TEST_F(TestIssue90, testBackward) {
+  bidirectional->setDirection("backward");
+  bidirectional->run();
+  checkResult(*bidirectional, final_path, final_res, final_cost);
+}
+
+} // namespace bidirectional
diff --git a/test/cc/src/test_labelling.cc b/test/cc/src/test_labelling.cc
index 0675908..877025e 100644
--- a/test/cc/src/test_labelling.cc
+++ b/test/cc/src/test_labelling.cc
@@ -1,10 +1,26 @@
-#include "test_labelling.h"
-
 #include <algorithm> // make_heap, push_heap
 #include <memory>    // make_unique
 
+#include "gtest/gtest.h"
+#include "src/cc/bidirectional.h"
+#include "src/cc/digraph.h"
+#include "src/cc/labelling.h"
+
 namespace labelling {
 
+class TestLabelling : public ::testing::Test {
+ protected:
+  double                                 weight     = 10.0;
+  bidirectional::Vertex                  node       = {1, 1}; // B
+  bidirectional::Vertex                  other_node = {2, 2}; // C
+  std::vector<double>                    res        = {6.0, 5.0};
+  std::vector<int>                       path       = {0};
+  std::vector<double>                    max_res    = {20.0, 20.0};
+  std::vector<double>                    min_res    = {0.0, 0.0};
+  std::unique_ptr<bidirectional::Params> params_ptr =
+      std::make_unique<bidirectional::Params>();
+};
+
 TEST_F(TestLabelling, testDominance) {
   const Label         label(weight, node, res, path, params_ptr.get());
   std::vector<double> res2 = {6.0, -3.0};
@@ -17,6 +33,41 @@ TEST_F(TestLabelling, testDominance) {
   ASSERT_FALSE(label3.checkDominance(label2, bidirectional::BWD));
 }
 
+TEST_F(TestLabelling, testDominanceElementary) {
+  params_ptr->elementary = true;
+  // L1
+  Label label(weight, node, res, path, params_ptr.get());
+  label.unreachable_nodes = std::set<int>({1, 2, 3});
+  // L2
+  std::vector<double> res2 = {6.0, 4.0};
+  Label               label2(weight, node, res2, path, params_ptr.get());
+  // Unrelated U2
+  label2.unreachable_nodes = std::set<int>({4, 5, 6});
+
+  // L2 dominates (due to resources)
+  ASSERT_FALSE(label.checkDominance(label2, bidirectional::FWD));
+  ASSERT_TRUE(label2.checkDominance(label, bidirectional::FWD));
+
+  // Make U2 \subset U1
+  label2.unreachable_nodes = std::set<int>({1, 2});
+  // L2 still dominates L1 now as U2 \subset U1
+  ASSERT_FALSE(label.checkDominance(label2, bidirectional::FWD));
+  ASSERT_TRUE(label2.checkDominance(label, bidirectional::FWD));
+
+  // Make U1 \subset U2
+  label2.unreachable_nodes = std::set<int>({1, 2, 3, 4});
+  // Neither dominate. As U2 is not a \subset U1
+  ASSERT_FALSE(label.checkDominance(label2, bidirectional::FWD));
+  ASSERT_FALSE(label2.checkDominance(label, bidirectional::FWD));
+
+  // Make U1 = U2
+  label2.unreachable_nodes = std::set<int>({1, 2, 3});
+  // L2 dominates as tie breaker because of resources. If we don't check for
+  // equality in checkDominance, neither would dominate.
+  ASSERT_FALSE(label.checkDominance(label2, bidirectional::FWD));
+  ASSERT_TRUE(label2.checkDominance(label, bidirectional::FWD));
+}
+
 TEST_F(TestLabelling, testThreshold) {
   const Label  label(weight, node, res, path, params_ptr.get());
   const double threshold1 = 11.0;
@@ -44,6 +95,17 @@ TEST_F(TestLabelling, testFeasibility) {
   ASSERT_FALSE(label.checkFeasibility(min_res, max_res));
 }
 
+TEST_F(TestLabelling, testFeasibilitySoft) {
+  const Label               label(weight, node, res, path, params_ptr.get());
+  const std::vector<double> min_res = {6.0, 10.0};
+
+  // Soft passes as critical resource is at index 0 and res[0] = 6.0 <=
+  // min_res[0] = 6.0, and index 1 is not checked as bound is not <= 0
+  ASSERT_TRUE(label.checkFeasibility(max_res, min_res, true));
+  // Hard fails as res[1] = 5.0 is not >= min_res[1] = 10.0
+  ASSERT_FALSE(label.checkFeasibility(max_res, min_res));
+}
+
 TEST_F(TestLabelling, testExtendForward) {
   Label label(weight, node, res, path, params_ptr.get());
   auto  labels                         = std::make_unique<std::vector<Label>>();
diff --git a/test/cc/src/test_labelling.h b/test/cc/src/test_labelling.h
deleted file mode 100644
index 598759c..0000000
--- a/test/cc/src/test_labelling.h
+++ /dev/null
@@ -1,26 +0,0 @@
-#ifndef TEST_TEST_LABELLING_H__
-#define TEST_TEST_LABELLING_H__
-
-#include "bidirectional.h"
-#include "digraph.h"
-#include "gtest/gtest.h"
-#include "labelling.h"
-
-namespace labelling {
-
-class TestLabelling : public ::testing::Test {
- protected:
-  double                                 weight     = 10.0;
-  bidirectional::Vertex                  node       = {1, 1}; // B
-  bidirectional::Vertex                  other_node = {2, 2}; // C
-  std::vector<double>                    res        = {6.0, 5.0};
-  std::vector<int>                       path       = {0};
-  std::vector<double>                    max_res    = {20.0, 20.0};
-  std::vector<double>                    min_res    = {0.0, 0.0};
-  std::unique_ptr<bidirectional::Params> params_ptr =
-      std::make_unique<bidirectional::Params>();
-};
-
-} // namespace labelling
-
-#endif // TEST_TEST_LABELLING_H__
diff --git a/test/cc/src/test_vrpy_issue_15.cc b/test/cc/src/test_vrpy_issue_15.cc
new file mode 100644
index 0000000..d1407ce
--- /dev/null
+++ b/test/cc/src/test_vrpy_issue_15.cc
@@ -0,0 +1,18 @@
+#include <memory>
+#include <numeric>
+#include <tuple>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "src/cc/bidirectional.h"
+
+namespace bidirectional {
+
+class TestVRPyIssue15 : public ::testing::Test {
+ protected:
+  const bool elementary = true;
+};
+
+TEST_F(TestVRPyIssue15, test) {}
+
+} // namespace bidirectional
diff --git a/test/cc/src/utils.cc b/test/cc/src/utils.cc
index 6e02052..56840bd 100644
--- a/test/cc/src/utils.cc
+++ b/test/cc/src/utils.cc
@@ -1,5 +1,7 @@
 #include "utils.h"
 
+#include "gtest/gtest.h"
+
 void skipLines(std::ifstream& file, std::string& line, const int& num_lines) {
   for (int i = 0; i < num_lines; ++i) {
     std::getline(file, line);
@@ -99,3 +101,13 @@ int getElapsedTime(const clock_t& start_time) {
   const double  timediff_ms = ((double)timediff) / 1000.0;
   return std::ceil(timediff_ms);
 }
+
+void checkResult(
+    const bidirectional::BiDirectional& alg,
+    const std::vector<int>&             path,
+    const std::vector<double>&          consumed_resources,
+    const double&                       cost) {
+  ASSERT_EQ(alg.getPath(), path);
+  ASSERT_EQ(alg.getConsumedResources(), consumed_resources);
+  ASSERT_EQ(alg.getTotalCost(), cost);
+}
diff --git a/test/cc/src/utils.h b/test/cc/src/utils.h
index b861e53..acaf7e7 100644
--- a/test/cc/src/utils.h
+++ b/test/cc/src/utils.h
@@ -8,7 +8,7 @@
 #include <string>
 #include <vector>
 
-// File related
+#include "src/cc/bidirectional.h"
 
 // Skip a number of lines in a file
 void skipLines(std::ifstream& file, std::string& line, const int& num_lines);
@@ -36,4 +36,10 @@ void writeToFile(
 // Get elapsed time since start time in milliseconds
 int getElapsedTime(const clock_t& start_time);
 
+void checkResult(
+    const bidirectional::BiDirectional& alg,
+    const std::vector<int>&             path,
+    const std::vector<double>&          consumed_resources,
+    const double&                       cost);
+
 #endif // TEST_UTILS_H__
diff --git a/test/python/tests_bidirectional.py b/test/python/tests_bidirectional.py
index 68faf95..c431286 100644
--- a/test/python/tests_bidirectional.py
+++ b/test/python/tests_bidirectional.py
@@ -1,13 +1,12 @@
-import unittest
 from time import time
 
 from networkx import DiGraph
-from numpy import array
 
 from cspy import BiDirectional
+from utils import TestingBase
 
 
-class TestsBiDirectional(unittest.TestCase):
+class TestsBiDirectional(TestingBase):
     """
     Tests for finding the resource constrained shortest
     path of simple DiGraph using the BiDirectional algorithm.
@@ -29,21 +28,6 @@ def setUp(self):
         self.total_cost = -13
         self.consumed_resources = [4, 15.3]
 
-    # TODO fix method="random" see issues
-    # @parameterized.expand(zip(range(100), range(100)))
-    # def test_random(self, _, seed):
-    #     'Test method = "random" for a range of seeds'
-    #     alg = BiDirectional(self.G,
-    #                         self.max_res,
-    #                         self.min_res,
-    #                         method="random",
-    #                         seed=seed)
-    #     # Run and test results
-    #     alg.run()
-    #     self.assertEqual(alg.path, self.result_path)
-    #     self.assertEqual(alg.total_cost, self.total_cost)
-    #     self.assertTrue(alg.consumed_resources == self.consumed_resources)
-
     def test_generated(self):
         'Test method = "generated"'
         alg = BiDirectional(self.G,
@@ -51,9 +35,8 @@ def test_generated(self):
                             self.min_res,
                             method="generated")
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_processed(self):
         'Test method = "processed"'
@@ -62,12 +45,8 @@ def test_processed(self):
                             self.min_res,
                             method="processed")
         alg.run()
-        path = alg.path
-        cost = alg.total_cost
-        total_res = alg.consumed_resources
-        self.assertEqual(path, self.result_path)
-        self.assertEqual(cost, self.total_cost)
-        self.assertTrue(total_res == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_unprocessed(self):
         'Test method = "unprocessed"'
@@ -76,9 +55,8 @@ def test_unprocessed(self):
                             self.min_res,
                             method="unprocessed")
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_unprocessed_time_limit(self):
         'Test time_limit parameter'
@@ -90,9 +68,8 @@ def test_unprocessed_time_limit(self):
         start = time()
         alg.run()
         self.assertTrue(time() - start <= 0.001 + 1e-3)
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_unprocessed_threshold(self):
         'Test threshold parameter'
@@ -102,9 +79,8 @@ def test_unprocessed_threshold(self):
                             method="unprocessed",
                             threshold=0)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_unprocessed_time_limit_threshold(self):
         'Test time_limit and threshold parameters'
@@ -117,9 +93,8 @@ def test_unprocessed_time_limit_threshold(self):
         start = time()
         alg.run()
         self.assertTrue(time() - start <= 0.001 + 1e-3)
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_forward(self):
         alg = BiDirectional(self.G,
@@ -127,9 +102,8 @@ def test_forward(self):
                             self.min_res,
                             direction='forward')
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_backward(self):
         alg = BiDirectional(self.G,
@@ -138,9 +112,8 @@ def test_backward(self):
                             direction='backward')
         # Check path
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_input_exceptions(self):
         # Check whether wrong input raises exceptions
diff --git a/test/python/tests_grasp.py b/test/python/tests_grasp.py
index 9037626..7a043b5 100644
--- a/test/python/tests_grasp.py
+++ b/test/python/tests_grasp.py
@@ -1,12 +1,12 @@
-import unittest
-
 from networkx import DiGraph
 from numpy import array
 
 from cspy.algorithms.grasp import GRASP
 
+from utils import TestingBase
+
 
-class TestsGRASP(unittest.TestCase):
+class TestsGRASP(TestingBase):
     """
     Tests for finding the resource constrained shortest
     path of simple DiGraph using the GRASP algorithm.
@@ -51,9 +51,8 @@ def test(self):
                     max_iter=50,
                     max_localiter=10)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(all(alg.consumed_resources == self.consumed_resources))
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_input_exceptions(self):
         # Check whether wrong input raises exceptions
diff --git a/test/python/tests_greedy_elimination.py b/test/python/tests_greedy_elimination.py
index fe5f958..65966bd 100644
--- a/test/python/tests_greedy_elimination.py
+++ b/test/python/tests_greedy_elimination.py
@@ -1,14 +1,14 @@
-import unittest
 from time import time
 
 from networkx import DiGraph
 from numpy import array
-from parameterized import parameterized
 
 from cspy import GreedyElim
 
+from utils import TestingBase
 
-class TestsGreedyElimination(unittest.TestCase):
+
+class TestsGreedyElimination(TestingBase):
     """
     Tests for finding the resource constrained shortest
     path of simple DiGraph using the BiDirectional algorithm.
@@ -34,17 +34,15 @@ def test_simple(self):
         'algorithm = "simple" (default)'
         alg = GreedyElim(self.G, self.max_res, self.min_res)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(all(alg.consumed_resources == self.consumed_resources))
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_astar(self):
         'algorithm = "astar"'
         alg = GreedyElim(self.G, self.max_res, self.min_res, algorithm="astar")
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(all(alg.consumed_resources == self.consumed_resources))
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_time_limit(self):
         'time limit parameter'
@@ -52,17 +50,14 @@ def test_time_limit(self):
         start = time()
         alg.run()
         self.assertTrue(time() - start <= 0.002)
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(all(alg.consumed_resources == self.consumed_resources))
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_threshold(self):
         'test threshold parameter'
         alg = GreedyElim(self.G, self.max_res, self.min_res, threshold=100)
         alg.run()
-        self.assertEqual(alg.path, ["Source", "A", "B", "Sink"])
-        self.assertEqual(alg.total_cost, 8)
-        self.assertTrue(all(alg.consumed_resources == [3, 4.3]))
+        self.check_result(alg, ["Source", "A", "B", "Sink"], 8, [3, 4.3])
 
     def test_time_limit_threshold(self):
         'time limit and threshold parameters'
@@ -74,9 +69,7 @@ def test_time_limit_threshold(self):
         start = time()
         alg.run()
         self.assertTrue(time() - start <= 0.002)
-        self.assertEqual(alg.path, ["Source", "A", "B", "Sink"])
-        self.assertEqual(alg.total_cost, 8)
-        self.assertTrue(all(alg.consumed_resources == [3, 4.3]))
+        self.check_result(alg, ["Source", "A", "B", "Sink"], 8, [3, 4.3])
 
     def test_astar_time_limit(self):
         'time limit parameter'
@@ -88,9 +81,8 @@ def test_astar_time_limit(self):
         start = time()
         alg.run()
         self.assertTrue(time() - start <= 0.002)
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(all(alg.consumed_resources == self.consumed_resources))
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_astar_threshold(self):
         'test threshold parameter'
@@ -100,9 +92,8 @@ def test_astar_threshold(self):
                          algorithm="astar",
                          threshold=100)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(all(alg.consumed_resources == self.consumed_resources))
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_astar_time_limit_threshold(self):
         'time limit and threshold parameters'
@@ -115,9 +106,8 @@ def test_astar_time_limit_threshold(self):
         start = time()
         alg.run()
         self.assertTrue(time() - start <= 0.002)
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(all(alg.consumed_resources == self.consumed_resources))
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_time_limit_raises(self):
         'Time limit of 0 raises an exception'
diff --git a/test/python/tests_issue17.py b/test/python/tests_issue17.py
index 49d34be..158cce0 100644
--- a/test/python/tests_issue17.py
+++ b/test/python/tests_issue17.py
@@ -1,13 +1,13 @@
-import unittest
-
 from networkx import DiGraph
 from numpy import array
 
 from cspy.algorithms.tabu import Tabu
 from cspy.algorithms.bidirectional import BiDirectional
 
+from utils import TestingBase
+
 
-class TestsIssue17(unittest.TestCase):
+class TestsIssue17(TestingBase):
     """
     Tests for issue #17
     https://github.com/torressa/cspy/issues/17
@@ -50,9 +50,8 @@ def test_bidirectional_forward(self):
                             direction='forward',
                             elementary=True)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
         self.assertTrue(
             all(e in self.G.edges() for e in zip(alg.path, alg.path[1:])))
 
@@ -63,17 +62,14 @@ def test_bidirectional_backward(self):
                             direction='backward',
                             elementary=True)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
         self.assertTrue(
             all(e in self.G.edges() for e in zip(alg.path, alg.path[1:])))
 
     def test_tabu(self):
         alg = Tabu(self.G, self.max_res, self.min_res)
         alg.run()
-        self.assertEqual(alg.total_cost, 1)
-        self.assertEqual(alg.path, ['Source', 2, 5, 4, 'Sink'])
-        self.assertTrue(all(alg.consumed_resources == [4, 4]))
+        self.check_result(alg, ['Source', 2, 5, 4, 'Sink'], 1, [4, 4])
         self.assertTrue(
             all(e in self.G.edges() for e in zip(alg.path, alg.path[1:])))
diff --git a/test/python/tests_issue20.py b/test/python/tests_issue20.py
index c64ff73..688771b 100644
--- a/test/python/tests_issue20.py
+++ b/test/python/tests_issue20.py
@@ -1,13 +1,13 @@
-import unittest
-
 from numpy import array
 from networkx import DiGraph
 
 from cspy.algorithms.tabu import Tabu
 from cspy.algorithms.bidirectional import BiDirectional
 
+from utils import TestingBase
+
 
-class TestsIssue20(unittest.TestCase):
+class TestsIssue20(TestingBase):
     """Tests for issue #20
     https://github.com/torressa/cspy/issues/20
     """
@@ -37,9 +37,8 @@ def test_bidirectional(self):
         """
         alg = BiDirectional(self.G, self.max_res, self.min_res, elementary=True)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertEqual(alg.consumed_resources, self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
         self.assertTrue(
             all(e in self.G.edges() for e in zip(alg.path, alg.path[1:])))
 
@@ -49,8 +48,7 @@ def test_tabu(self):
         """
         alg = Tabu(self.G, self.max_res, self.min_res)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(all(alg.consumed_resources == self.consumed_resources))
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
         self.assertTrue(
             all(e in self.G.edges() for e in zip(alg.path, alg.path[1:])))
diff --git a/test/python/tests_issue22.py b/test/python/tests_issue22.py
index 455179e..63ae953 100644
--- a/test/python/tests_issue22.py
+++ b/test/python/tests_issue22.py
@@ -1,14 +1,13 @@
-import unittest
-
 from numpy import array
-from random import randint
 from networkx import DiGraph, astar_path
 
 from cspy.algorithms.tabu import Tabu
 from cspy.algorithms.bidirectional import BiDirectional
 
+from utils import TestingBase
+
 
-class TestsIssue22(unittest.TestCase):
+class TestsIssue22(TestingBase):
     """
     Tests for issue #22
     https://github.com/torressa/cspy/issues/22
@@ -37,9 +36,8 @@ def test_tabu(self):
         """
         alg = Tabu(self.G, self.max_res, self.min_res)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(all(alg.consumed_resources == self.consumed_resources))
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
         self.assertTrue(
             all(e in self.G.edges() for e in zip(alg.path, alg.path[1:])))
         # Check networkx's astar_path
@@ -56,9 +54,8 @@ def test_bidirectional(self):
                             self.min_res,
                             elementary=False)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertEqual(alg.consumed_resources, self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_bidirectional_forward(self):
         """
@@ -71,9 +68,8 @@ def test_bidirectional_forward(self):
                             direction='forward',
                             elementary=True)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_bidirectional_backward(self):
         """
@@ -86,6 +82,5 @@ def test_bidirectional_backward(self):
                             direction='backward',
                             elementary=True)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
diff --git a/test/python/tests_issue25.py b/test/python/tests_issue25.py
index 69774d2..23eb0da 100644
--- a/test/python/tests_issue25.py
+++ b/test/python/tests_issue25.py
@@ -1,12 +1,12 @@
-import unittest
-
 from numpy import array
 from networkx import DiGraph
 
 from cspy.algorithms.bidirectional import BiDirectional
 
+from utils import TestingBase
+
 
-class TestsIssue25(unittest.TestCase):
+class TestsIssue25(TestingBase):
     """
     Tests for issue #25
     https://github.com/torressa/cspy/issues/25
@@ -30,6 +30,5 @@ def setUp(self):
     def test_bidirectional(self):
         alg = BiDirectional(self.G, self.max_res, self.min_res, elementary=True)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
diff --git a/test/python/tests_issue32.py b/test/python/tests_issue32.py
index e8eeaf7..5528f18 100644
--- a/test/python/tests_issue32.py
+++ b/test/python/tests_issue32.py
@@ -1,9 +1,9 @@
-import unittest
-
 from numpy import array
 from networkx import DiGraph
 
-from cspy import BiDirectional, Tabu, GreedyElim, REFCallback
+from cspy import BiDirectional, GreedyElim, REFCallback
+
+from utils import TestingBase
 
 
 class MyCallback(REFCallback):
@@ -19,7 +19,7 @@ def REF_fwd(self, cumul_res, tail, head, edge_res, partial_path,
         res_new = list(cumul_res)
         # Monotone resource
         res_new[0] += 1.0
-        tail_original, head_original = self._get_original_node_label(tail, head)
+        _, head_original = self._get_original_node_label(tail, head)
         # Increasing resource
         if head_original != "Sink":
             res_new[1] += float(int(head_original)**2)
@@ -30,7 +30,7 @@ def REF_fwd(self, cumul_res, tail, head, edge_res, partial_path,
     def REF_bwd(self, cumul_res, tail, head, edge_res, partial_path,
                 cumul_cost):
         res_new = list(cumul_res)
-        tail_original, head_original = self._get_original_node_label(tail, head)
+        _, head_original = self._get_original_node_label(tail, head)
         # Monotone resource
         res_new[0] -= 1
         # Increasing resource
@@ -45,8 +45,7 @@ def REF_join(self, fwd_resources, bwd_resources, tail, head,
         # local copies
         fwd_res = list(fwd_resources)
         bwd_res = list(bwd_resources)
-        edge_res = list(edge_resources)
-        tail_original, head_original = self._get_original_node_label(tail, head)
+        _, head_original = self._get_original_node_label(tail, head)
         # Compute merged resources
         merged_res = [0] * len(fwd_res)
         merged_res[0] = fwd_res[0] + bwd_res[0]
@@ -64,7 +63,7 @@ def _get_original_node_label(self, tail, head):
         return _tail, _head
 
 
-class TestsIssue32(unittest.TestCase):
+class TestsIssue32(TestingBase):
     """
     Tests for issue #32
     https://github.com/torressa/cspy/issues/32
@@ -103,9 +102,8 @@ def test_bidirectional(self):
         # Overwrite graph as original labelling won't match
         self.my_callback.G = alg.G
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertEqual(alg.consumed_resources, self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_forward(self):
         """
@@ -118,9 +116,8 @@ def test_forward(self):
                             REF_callback=self.my_callback)
         self.my_callback.G = alg.G
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertEqual(alg.consumed_resources, self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     # def test_tabu(self):
     #     alg = Tabu(self.G,
diff --git a/test/python/tests_issue38.py b/test/python/tests_issue38.py
index 7ccc7e0..6da1dd5 100644
--- a/test/python/tests_issue38.py
+++ b/test/python/tests_issue38.py
@@ -1,12 +1,12 @@
-import unittest
-
 from numpy import array
 from networkx import DiGraph
 
 from cspy.algorithms.bidirectional import BiDirectional
 
+from utils import TestingBase
+
 
-class TestsIssue38(unittest.TestCase):
+class TestsIssue38(TestingBase):
     """
     Tests for issue #38
     https://github.com/torressa/cspy/issues/38
@@ -20,10 +20,12 @@ def setUp(self):
         self.G = DiGraph(directed=True, n_res=2)
         self.G.add_edge("Source", "A", res_cost=array([1, 2]), weight=0)
         self.G.add_edge("A", "Sink", res_cost=array([1, 10]), weight=0)
+        self.result_path = ['Source', "A", 'Sink']
+        self.total_cost = 0
+        self.consumed_resources = [2, 12]
 
     def test_bidirectional(self):
         alg = BiDirectional(self.G, self.max_res, self.min_res)
         alg.run()
-        self.assertEqual(alg.path, ['Source', "A", 'Sink'])
-        self.assertEqual(alg.total_cost, 0)
-        self.assertTrue(alg.consumed_resources == [2, 12])
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
diff --git a/test/python/tests_issue41.py b/test/python/tests_issue41.py
index 7376206..7c0ead5 100644
--- a/test/python/tests_issue41.py
+++ b/test/python/tests_issue41.py
@@ -1,12 +1,12 @@
-import unittest
-
 from numpy import array
 from networkx import DiGraph
 
 from cspy import BiDirectional
 
+from utils import TestingBase
+
 
-class TestsIssue41(unittest.TestCase):
+class TestsIssue41(TestingBase):
     """
     Tests for issue #41
     https://github.com/torressa/cspy/issues/41
@@ -28,22 +28,6 @@ def setUp(self):
         self.total_cost = 20
         self.consumed_resources = [3, 3]
 
-    # @parameterized.expand(zip(range(100), range(100)))
-    # def test_bidirectional_random(self, _, seed):
-    #     """
-    #     Test BiDirectional with randomly chosen sequence of directions
-    #     for a range of seeds.
-    #     """
-    #     alg = BiDirectional(self.G,
-    #                         self.max_res,
-    #                         self.min_res,
-    #                         method="random",
-    #                         seed=seed)
-    #     alg.run()
-    #     self.assertEqual(alg.path, self.result_path)
-    #     self.assertEqual(alg.total_cost, self.total_cost)
-    #     self.assertTrue(all(alg.consumed_resources == self.consumed_resources))
-
     def test_bidirectional(self):
         """
         Test BiDirectional with randomly chosen sequence of directions
@@ -51,9 +35,8 @@ def test_bidirectional(self):
         """
         alg = BiDirectional(self.G, self.max_res, self.min_res)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_bidirectional_forward(self):
         alg = BiDirectional(self.G,
@@ -61,9 +44,8 @@ def test_bidirectional_forward(self):
                             self.min_res,
                             direction='forward')
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_bidirectional_backward(self):
         alg = BiDirectional(self.G,
@@ -71,6 +53,5 @@ def test_bidirectional_backward(self):
                             self.min_res,
                             direction='backward')
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
diff --git a/test/python/tests_issue52.py b/test/python/tests_issue52.py
index f03bb52..1d130ee 100644
--- a/test/python/tests_issue52.py
+++ b/test/python/tests_issue52.py
@@ -1,12 +1,12 @@
-import unittest
-
 from numpy import array
 from networkx import DiGraph
 
 from cspy import BiDirectional
 
+from utils import TestingBase
+
 
-class TestsIssue52(unittest.TestCase):
+class TestsIssue52(TestingBase):
     """
     Tests for issue #52
     https://github.com/torressa/cspy/issues/52
@@ -34,9 +34,8 @@ def test_bidirectional(self):
         """
         alg = BiDirectional(self.G, self.max_res, self.min_res)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertEqual(alg.consumed_resources, self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_bidirectional_forward(self):
         alg = BiDirectional(self.G,
@@ -44,9 +43,8 @@ def test_bidirectional_forward(self):
                             self.min_res,
                             direction='forward')
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_bidirectional_backward(self):
         alg = BiDirectional(self.G,
@@ -54,6 +52,5 @@ def test_bidirectional_backward(self):
                             self.min_res,
                             direction='backward')
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
diff --git a/test/python/tests_issue69.py b/test/python/tests_issue69.py
index 16343fe..3d6de18 100644
--- a/test/python/tests_issue69.py
+++ b/test/python/tests_issue69.py
@@ -1,12 +1,12 @@
-import unittest
-
 from numpy import array
 from networkx import DiGraph
 
 from cspy import BiDirectional
 
+from utils import TestingBase
+
 
-class TestsIssue69(unittest.TestCase):
+class TestsIssue69(TestingBase):
     """
     Tests for issue #69
     https://github.com/torressa/cspy/issues/69
@@ -35,9 +35,8 @@ def test_bidirectional(self):
         """
         alg = BiDirectional(self.G, self.max_res, self.min_res)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertEqual(alg.consumed_resources, self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_forward(self):
         alg = BiDirectional(self.G,
@@ -45,9 +44,8 @@ def test_forward(self):
                             self.min_res,
                             direction='forward')
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_backward(self):
         alg = BiDirectional(self.G,
@@ -55,6 +53,5 @@ def test_backward(self):
                             self.min_res,
                             direction='backward')
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(alg.consumed_resources == self.consumed_resources)
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
diff --git a/test/python/tests_issue79.py b/test/python/tests_issue79.py
index 327fd48..473100c 100644
--- a/test/python/tests_issue79.py
+++ b/test/python/tests_issue79.py
@@ -1,13 +1,13 @@
-import unittest
-
-from numpy import array, testing
+from numpy import array
 import numpy as np
 from networkx import DiGraph
 
 from cspy import PSOLGENT
 
+from utils import TestingBase
+
 
-class TestsIssue79(unittest.TestCase):
+class TestsIssue79(TestingBase):
     """
     Tests for issue #79
     https://github.com/torressa/cspy/issues/79
@@ -39,21 +39,22 @@ def test_direct_connect(self):
         """
         alg = PSOLGENT(self.G, self.max_res, self.min_res, seed=42)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertIsNone(testing.assert_allclose(alg.consumed_resources,
-                          self.consumed_resources))
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_include_source_sink(self):
         """
         Test PSOLGENT can find path of Source directly connected to Sink
         """
-        alg = PSOLGENT(self.G, self.max_res, self.min_res, seed=42,
-                       lower_bound=-2, upper_bound=2)
+        alg = PSOLGENT(self.G,
+                       self.max_res,
+                       self.min_res,
+                       seed=42,
+                       lower_bound=-2,
+                       upper_bound=2)
         alg.run()
         # Check that all considered paths include source/sink
-        self.assertLessEqual(np.max(alg.pos[:,[0,-1]]),
-                          self.pos_limit)
+        self.assertLessEqual(np.max(alg.pos[:, [0, -1]]), self.pos_limit)
 
     def test_sorting_nodes(self):
         """
@@ -61,5 +62,3 @@ def test_sorting_nodes(self):
         """
         sorted_nodes = PSOLGENT._sort_nodes(self.nodes)
         self.assertListEqual(sorted_nodes, self.expected_nodes)
-
-
diff --git a/test/python/tests_issue82.py b/test/python/tests_issue82.py
index bbde0bf..409c611 100644
--- a/test/python/tests_issue82.py
+++ b/test/python/tests_issue82.py
@@ -1,13 +1,12 @@
-import unittest
-
-from numpy import array, testing
-import numpy as np
+from numpy import array
 from networkx import DiGraph
 
 from cspy import PSOLGENT
 
+from utils import TestingBase
+
 
-class TestsIssue82(unittest.TestCase):
+class TestsIssue82(TestingBase):
     """
     Tests for issue #82
     https://github.com/torressa/cspy/issues/82
@@ -18,12 +17,12 @@ def setUp(self):
         self.max_res, self.min_res = [10], [0]
         # Create simple digraph with appropriate attributes
         self.G = DiGraph(directed=True, n_res=1)
-        self.G.add_edge("Source", 'A', res_cost=np.array([1]), weight=8)
-        self.G.add_edge('A', 'B', res_cost=np.array([9]), weight=8)
-        self.G.add_edge('A', 'Sink', res_cost=np.array([1]), weight=1)
-        self.G.add_edge('Source', 'B', res_cost=np.array([1]), weight=1)
-        self.G.add_edge('B', 'Sink', res_cost=np.array([10]), weight=1)
-        self.G.add_edge('B', 'A', res_cost=np.array([1]), weight=1)
+        self.G.add_edge("Source", 'A', res_cost=array([1]), weight=8)
+        self.G.add_edge('A', 'B', res_cost=array([9]), weight=8)
+        self.G.add_edge('A', 'Sink', res_cost=array([1]), weight=1)
+        self.G.add_edge('Source', 'B', res_cost=array([1]), weight=1)
+        self.G.add_edge('B', 'Sink', res_cost=array([10]), weight=1)
+        self.G.add_edge('B', 'A', res_cost=array([1]), weight=1)
         # Expected CSP results
         self.result_path = ['Source', "B", "A", 'Sink']
         self.total_cost = 3.0
@@ -32,8 +31,5 @@ def setUp(self):
     def test_PSOLGENT(self):
         alg = PSOLGENT(self.G, self.max_res, self.min_res)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertIsNone(
-            testing.assert_allclose(alg.consumed_resources,
-                                    self.consumed_resources))
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
diff --git a/test/python/tests_issue89.py b/test/python/tests_issue89.py
new file mode 100644
index 0000000..b5d3db7
--- /dev/null
+++ b/test/python/tests_issue89.py
@@ -0,0 +1,126 @@
+from networkx import DiGraph
+
+from cspy import BiDirectional
+
+from utils import TestingBase
+
+
+class TestsIssue89(TestingBase):
+    """
+    Tests for issue #89
+    https://github.com/torressa/cspy/issues/89
+    """
+
+    def setUp(self):
+        self.max_res = [10, 100]
+        self.min_res = [0, 1]
+        rc = [1, 0]
+
+        # Example 1
+        # Source -> 0 -> Sink is valid, but not returned.
+        self.H = DiGraph(directed=True, n_res=2)
+        self.H.add_nodes_from([0, 1, 2, 3, 4, 5, 6, 7])
+
+        self.H.add_edge(0, 3, res_cost=rc, weight=1)
+        self.H.add_edge(0, 5, res_cost=rc, weight=1)
+        self.H.add_edge(0, 'Sink', res_cost=rc, weight=1)
+        self.H.add_edge(1, 3, res_cost=rc, weight=1)
+        self.H.add_edge(1, 5, res_cost=rc, weight=1)
+        self.H.add_edge(1, 'Sink', res_cost=rc, weight=1)
+        self.H.add_edge(2, 1, res_cost=rc, weight=1)
+        self.H.add_edge(2, 4, res_cost=rc, weight=1)
+        self.H.add_edge(2, 'Sink', res_cost=rc, weight=1)
+        self.H.add_edge(3, 1, res_cost=rc, weight=1)
+        self.H.add_edge(3, 4, res_cost=rc, weight=1)
+        self.H.add_edge(3, 'Sink', res_cost=rc, weight=1)
+        self.H.add_edge(4, 0, res_cost=rc, weight=1)
+        self.H.add_edge(4, 2, res_cost=rc, weight=1)
+        self.H.add_edge(4, 'Sink', res_cost=rc, weight=1)
+        self.H.add_edge(5, 0, res_cost=rc, weight=1)
+        self.H.add_edge(5, 2, res_cost=rc, weight=1)
+        self.H.add_edge(5, 'Sink', res_cost=rc, weight=1)
+        self.H.add_edge('Source', 0, res_cost=[1, 1], weight=1)
+        self.H.add_edge('Source', 1, res_cost=rc, weight=1)
+        self.H.add_edge('Source', 2, res_cost=rc, weight=1)
+        self.H.add_edge('Source', 3, res_cost=rc, weight=1)
+        self.H.add_edge('Source', 4, res_cost=rc, weight=1)
+        self.H.add_edge('Source', 5, res_cost=rc, weight=1)
+
+        # Example 2 (subgraph of example 1)
+        self.H2 = DiGraph(directed=True, n_res=2)
+        self.H2.add_nodes_from([0, 1, 2, 3, 4, 5, 6, 7])
+
+        self.H2.add_edge(0, 'Sink', res_cost=rc, weight=1)
+        self.H2.add_edge(1, 'Sink', res_cost=rc, weight=1)
+        self.H2.add_edge('Source', 0, res_cost=[1, 1], weight=1)
+        self.H2.add_edge('Source', 1, res_cost=rc, weight=1)
+        self.result_path = ['Source', 0, 'Sink']
+        self.total_cost = 2.0
+        self.consumed_resources = [2.0, 1.0]
+
+    def test_forward_elementary(self):
+        alg = BiDirectional(self.H,
+                            self.max_res,
+                            self.min_res,
+                            elementary=True,
+                            direction="forward")
+
+        alg.run()
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
+
+    def test_forward(self):
+        alg = BiDirectional(self.H,
+                            self.max_res,
+                            self.min_res,
+                            direction="forward")
+
+        alg.run()
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
+
+    def test_both_elementary(self):
+        alg = BiDirectional(self.H, self.max_res, self.min_res, elementary=True)
+        alg.run()
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
+
+    def test_both(self):
+        alg = BiDirectional(self.H, self.max_res, self.min_res)
+        alg.run()
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
+
+    def test_H2_forward_elementary(self):
+        alg = BiDirectional(self.H2,
+                            self.max_res,
+                            self.min_res,
+                            elementary=True,
+                            direction="forward")
+        alg.run()
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
+
+    def test_H2_forward(self):
+        alg = BiDirectional(self.H2,
+                            self.max_res,
+                            self.min_res,
+                            direction="forward")
+        alg.run()
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
+
+    def test_H2_both_elementary(self):
+        alg = BiDirectional(self.H2,
+                            self.max_res,
+                            self.min_res,
+                            elementary=True)
+        alg.run()
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
+
+    def test_H2_both(self):
+        alg = BiDirectional(self.H2, self.max_res, self.min_res)
+        alg.run()
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
diff --git a/test/python/tests_issue90.py b/test/python/tests_issue90.py
new file mode 100644
index 0000000..7871d09
--- /dev/null
+++ b/test/python/tests_issue90.py
@@ -0,0 +1,105 @@
+from networkx import DiGraph
+
+from cspy import BiDirectional
+
+from utils import TestingBase
+
+
+def createG():
+    H = DiGraph(directed=True, n_res=2)
+    H.add_nodes_from(['Source', 1, 2, 3, 4, 'Sink'])
+    H.add_edge(2, 3, res_cost=[1, 1], weight=1)
+    H.add_edge(3, 4, res_cost=[1, 1], weight=1)
+    H.add_edge(4, 'Sink', res_cost=[1, 1], weight=1)
+    return H
+
+
+class TestsIssue90(TestingBase):
+    """
+    Tests for issue #90
+    https://github.com/torressa/cspy/issues/90
+    """
+
+    def setUp(self):
+        self.max_res = [6, 100]
+        self.min_res = [0, -100]
+
+        # This works
+        self.H = createG()
+        self.H.add_edge('Source', 1, res_cost=[1, 1], weight=1)
+        self.H.add_edge(1, 2, res_cost=[1, -1], weight=1)
+
+        self.H2 = createG()
+        self.H2.add_edge('Source', 1, res_cost=[1, -1], weight=1)
+        self.H2.add_edge(1, 2, res_cost=[1, 1], weight=1)
+
+        self.result_path = ['Source', 1, 2, 3, 4, 'Sink']
+        self.total_cost = 5.0
+        self.consumed_resources = [5.0, 3.0]
+
+    def test_forward_elementary(self):
+        alg = BiDirectional(self.H,
+                            self.max_res,
+                            self.min_res,
+                            elementary=True,
+                            direction="forward")
+
+        alg.run()
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
+
+    def test_forward(self):
+        alg = BiDirectional(self.H,
+                            self.max_res,
+                            self.min_res,
+                            direction="forward")
+
+        alg.run()
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
+
+    def test_both_elementary(self):
+        alg = BiDirectional(self.H, self.max_res, self.min_res, elementary=True)
+        alg.run()
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
+
+    def test_both(self):
+        alg = BiDirectional(self.H, self.max_res, self.min_res)
+        alg.run()
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
+
+    def test_H2_forward_elementary(self):
+        alg = BiDirectional(self.H2,
+                            self.max_res,
+                            self.min_res,
+                            elementary=True,
+                            direction="forward")
+        alg.run()
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
+
+    def test_H2_forward(self):
+        alg = BiDirectional(self.H2,
+                            self.max_res,
+                            self.min_res,
+                            direction="forward")
+        alg.run()
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
+
+    def test_H2_both_elementary(self):
+        alg = BiDirectional(self.H2,
+                            self.max_res,
+                            self.min_res,
+                            elementary=True)
+        alg.run()
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
+
+    def test_H2_both(self):
+        alg = BiDirectional(self.H2, self.max_res, self.min_res)
+        alg.run()
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
diff --git a/test/python/tests_preprocessing.py b/test/python/tests_preprocessing.py
index 16ff60b..4d6f38f 100644
--- a/test/python/tests_preprocessing.py
+++ b/test/python/tests_preprocessing.py
@@ -39,7 +39,6 @@ def setUp(self):
         self.F.add_edge('A', 'B', res_cost=array([10]), weight=1)
         self.F.add_edge('A', 'Sink', res_cost=array([1]), weight=1)
 
-
     def testUnreachable(self):
         """
         Tests if the unreachable node 'E' is removed.
diff --git a/test/python/tests_psolgent.py b/test/python/tests_psolgent.py
index 6a11492..662ff98 100644
--- a/test/python/tests_psolgent.py
+++ b/test/python/tests_psolgent.py
@@ -1,13 +1,13 @@
-import unittest
-
 from networkx import DiGraph
 from numpy import array
 from numpy.random import RandomState
 
 from cspy.algorithms.psolgent import PSOLGENT
 
+from utils import TestingBase
+
 
-class TestsPSOLGENT(unittest.TestCase):
+class TestsPSOLGENT(TestingBase):
     """
     Tests for finding the resource constrained shortest
     path of simple DiGraph using the PSOLGENT algorithm.
@@ -39,6 +39,9 @@ def setUp(self):
         # Resource infeasible edge
         self.G.add_edge('F', 'Sink', res_cost=array([10, 1]), weight=1)
         self.G.add_edge('E', 'Sink', res_cost=array([1, 1]), weight=1)
+        self.result_path = ['Source', 'A', 'C', 'D', 'E', 'Sink']
+        self.total_cost = 3
+        self.consumed_resources = [5, 5]
 
     def testPSOLGENT(self):
         psolgent = PSOLGENT(self.G,
@@ -53,17 +56,9 @@ def testPSOLGENT(self):
         self.assertTrue("run()" in str(context.exception))
         # Run and test results
         psolgent.run()
-        path = psolgent.path
-        cost = psolgent.total_cost
-        total_res = psolgent.consumed_resources
-        self.assertEqual(path, ['Source', 'A', 'C', 'D', 'E', 'Sink'])
-        self.assertEqual(cost, 3)
-        self.assertTrue(all(total_res == [5, 5]))
+        self.check_result(psolgent, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def testInputExceptions(self):
         # Check whether wrong input raises exceptions
         self.assertRaises(Exception, PSOLGENT, self.G, 'x', [1, 'foo'], 'up')
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/test/python/tests_tabu.py b/test/python/tests_tabu.py
index bfa6547..e609475 100644
--- a/test/python/tests_tabu.py
+++ b/test/python/tests_tabu.py
@@ -6,8 +6,10 @@
 
 from cspy.algorithms.tabu import Tabu
 
+from utils import TestingBase
 
-class TestsTabu(unittest.TestCase):
+
+class TestsTabu(TestingBase):
     """
     Tests for finding the resource constrained shortest
     path of simple DiGraph using the Tabu algorithm.
@@ -31,16 +33,14 @@ def setUp(self):
     def test_simple(self):
         alg = Tabu(self.G, self.max_res, self.min_res)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(all(alg.consumed_resources == self.consumed_resources))
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_astar(self):
         alg = Tabu(self.G, self.max_res, self.min_res, algorithm="astar")
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(all(alg.consumed_resources == self.consumed_resources))
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_time_limit(self):
         alg = Tabu(self.G, self.max_res, self.min_res, time_limit=0.001)
@@ -48,16 +48,13 @@ def test_time_limit(self):
         alg.run()
         # Fudge for windows workflow
         self.assertTrue(time() - start <= 0.002)
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(all(alg.consumed_resources == self.consumed_resources))
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_threshold(self):
         alg = Tabu(self.G, self.max_res, self.min_res, threshold=100)
         alg.run()
-        self.assertEqual(alg.path, ["Source", "A", "B", "Sink"])
-        self.assertEqual(alg.total_cost, 8)
-        self.assertTrue(all(alg.consumed_resources == [3, 4.3]))
+        self.check_result(alg, ["Source", "A", "B", "Sink"], 8, [3, 4.3])
 
     def test_time_limit_threshold(self):
         alg = Tabu(self.G,
@@ -68,9 +65,8 @@ def test_time_limit_threshold(self):
         start = time()
         alg.run()
         self.assertTrue(time() - start <= 0.002)
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(all(alg.consumed_resources == self.consumed_resources))
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_astar_time_limit(self):
         'time limit parameter'
@@ -82,9 +78,8 @@ def test_astar_time_limit(self):
         start = time()
         alg.run()
         self.assertTrue(time() - start <= 0.002)
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(all(alg.consumed_resources == self.consumed_resources))
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_astar_threshold(self):
         'test threshold parameter'
@@ -94,9 +89,8 @@ def test_astar_threshold(self):
                    algorithm="astar",
                    threshold=100)
         alg.run()
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(all(alg.consumed_resources == self.consumed_resources))
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_astar_time_limit_threshold(self):
         'time limit and threshold parameters'
@@ -109,9 +103,8 @@ def test_astar_time_limit_threshold(self):
         start = time()
         alg.run()
         self.assertTrue(time() - start <= 0.002)
-        self.assertEqual(alg.path, self.result_path)
-        self.assertEqual(alg.total_cost, self.total_cost)
-        self.assertTrue(all(alg.consumed_resources == self.consumed_resources))
+        self.check_result(alg, self.result_path, self.total_cost,
+                          self.consumed_resources)
 
     def test_time_limit_raises(self):
         alg = Tabu(self.G, self.max_res, self.min_res, time_limit=0)
diff --git a/test/python/utils.py b/test/python/utils.py
new file mode 100644
index 0000000..6c985cd
--- /dev/null
+++ b/test/python/utils.py
@@ -0,0 +1,12 @@
+import unittest
+
+from numpy import testing
+
+
+class TestingBase(unittest.TestCase):
+
+    def check_result(self, alg, path, cost, resources) -> bool:
+        self.assertEqual(alg.path, path)
+        self.assertEqual(alg.total_cost, cost)
+        self.assertIsNone(
+            testing.assert_allclose(alg.consumed_resources, resources))