Repository: oppia/oppia

Issue:
Error while running oppia on development server
I followed all the steps given on the installation guide page. After getting all the prerequisites, I tried to run the python -m scripts.start on the terminal. I then came across the following issue. Can somebody help me in solving this.(I am running Ubuntu on Virtual Box.)

Downloading Skulpt
Cloning into 'skulpt'...
remote: Enumerating objects: 29783, done.
remote: Total 29783 (delta 0), reused 0 (delta 0), pack-reused 29783
Receiving objects: 100% (29783/29783), 83.39 MiB | 1.65 MiB/s, done.
Resolving deltas: 100% (16983/16983), done.
Checking out files: 100% (3566/3566), done.
M	example/webglq2/q2bspload.py
M	src/lib/pythonds/basic/deque.py
M	src/lib/pythonds/basic/queue.py
M	src/lib/pythonds/basic/stack.py
Note: checking out '0.10.0'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at b431c9ca bump to better semantic version
Compiling Skulpt
+----------------------------------------------------------------------------+
GitPython is not installed for Python 2.6
The 'dist' command will not work without it.  Get it using pip or easy_install
or see:  http://packages.python.org/GitPython/0.3.1/intro.html#getting-started
+----------------------------------------------------------------------------+
support/d8/d8x64: error while loading shared libraries: libreadline.so.6: cannot open shared object file: No such file or directory
Tests failed on compressed version.
Installing pre-commit hook for git
Symlink already exists
Making pre-commit hook file executable ...
pre-commit hook file is now executable!
Installing pre-push hook for git
Symlink already exists
**Making pre-push hook file executable ...
Traceback (most recent call last):
  File "/usr/lib/python2.7/runpy.py", line 174, in _run_module_as_main
    "__main__", fname, loader, pkg_name)
  File "/usr/lib/python2.7/runpy.py", line 72, in _run_code
    exec code in run_globals
  File "/home/sudipta/Desktop/opensource/oppia/scripts/start.py", line 32, in <module>
    install_third_party_libs.main(args=[])
  File "scripts/install_third_party_libs.py", line 279, in main
    pre_push_hook.main(args=['--install'])
  File "scripts/pre_push_hook.py", line 352, in main
    install_hook()
  File "scripts/pre_push_hook.py", line 321, in install_hook
    raise ValueError(err_chmod_cmd)
ValueError: chmod: cannot operate on dangling symlink '/home/sudipta/Desktop/opensource/oppia/.git/hooks/pre-push'**

Error while running oppia on development server
I followed all the steps given on the installation guide page. After getting all the prerequisites, I tried to run the python -m scripts.start on the terminal. I then came across the following issue. Can somebody help me in solving this.(I am running Ubuntu on Virtual Box.)

Downloading Skulpt
Cloning into 'skulpt'...
remote: Enumerating objects: 29783, done.
remote: Total 29783 (delta 0), reused 0 (delta 0), pack-reused 29783
Receiving objects: 100% (29783/29783), 83.39 MiB | 1.65 MiB/s, done.
Resolving deltas: 100% (16983/16983), done.
Checking out files: 100% (3566/3566), done.
M	example/webglq2/q2bspload.py
M	src/lib/pythonds/basic/deque.py
M	src/lib/pythonds/basic/queue.py
M	src/lib/pythonds/basic/stack.py
Note: checking out '0.10.0'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at b431c9ca bump to better semantic version
Compiling Skulpt
+----------------------------------------------------------------------------+
GitPython is not installed for Python 2.6
The 'dist' command will not work without it.  Get it using pip or easy_install
or see:  http://packages.python.org/GitPython/0.3.1/intro.html#getting-started
+----------------------------------------------------------------------------+
support/d8/d8x64: error while loading shared libraries: libreadline.so.6: cannot open shared object file: No such file or directory
Tests failed on compressed version.
Installing pre-commit hook for git
Symlink already exists
Making pre-commit hook file executable ...
pre-commit hook file is now executable!
Installing pre-push hook for git
Symlink already exists
**Making pre-push hook file executable ...
Traceback (most recent call last):
  File "/usr/lib/python2.7/runpy.py", line 174, in _run_module_as_main
    "__main__", fname, loader, pkg_name)
  File "/usr/lib/python2.7/runpy.py", line 72, in _run_code
    exec code in run_globals
  File "/home/sudipta/Desktop/opensource/oppia/scripts/start.py", line 32, in <module>
    install_third_party_libs.main(args=[])
  File "scripts/install_third_party_libs.py", line 279, in main
    pre_push_hook.main(args=['--install'])
  File "scripts/pre_push_hook.py", line 352, in main
    install_hook()
  File "scripts/pre_push_hook.py", line 321, in install_hook
    raise ValueError(err_chmod_cmd)
ValueError: chmod: cannot operate on dangling symlink '/home/sudipta/Desktop/opensource/oppia/.git/hooks/pre-push'**



Discussion:
I'm getting the same error :
`Installing pre-commit hook for git
Symlink already exists
Making pre-commit hook file executable ...
pre-commit hook file is now executable!
Installing pre-push hook for git
Symlink already exists
Making pre-push hook file executable ...
Traceback (most recent call last):
  File "/usr/lib/python2.7/runpy.py", line 174, in _run_module_as_main
    "__main__", fname, loader, pkg_name)
  File "/usr/lib/python2.7/runpy.py", line 72, in _run_code
    exec code in run_globals
  File "/home/rajkumar/Documents/projects/oppia/scripts/start.py", line 33, in <module>
    install_third_party_libs.main()
  File "scripts/install_third_party_libs.py", line 225, in main
    pre_push_hook.main(args=['--install'])
  File "scripts/pre_push_hook.py", line 370, in main
    install_hook()
  File "scripts/pre_push_hook.py", line 322, in install_hook
    raise ValueError(err_chmod_cmd)
ValueError: chmod: cannot operate on dangling symlink '/home/rajkumar/Documents/projects/oppia/.git/hooks/pre-push'`


Reopening this because I am also getting this error. I'm looking into a fix now. @sudiptapradhan1181 @rajkumaar23 -- any workarounds that you remember?
Solution:
```
unlink /path/to/.../opensource/oppia/.git/hooks/pre-push
python -m scripts.start
```
Thanks @EricZLou! 

One question. Does this need to be added to our setup scripts? Why did it happen in the first place / how can we auto-detect and fix this so that it's not a problem for contributors in the future?
Hi @EricZLou, you need to sign the CLA before you can get assigned to issues. Follow the instructions <a href="https://github.com/oppia/oppia/wiki/Contributing-code-to-Oppia#setting-things-up">here</a> to get started. I am unassigning you for now, feel free to assign yourself once you have signed the CLA. Thanks!
I'm not sure why it happened in the first place as I was working off a completely clean oppia repo that I just cloned. But, we could always do some error checking in the `pre_push_hook.py` script like such:
Re: https://github.com/oppia/oppia/blob/develop/scripts/pre_push_hook.py#L334
```
if os.path.islink(pre_push_file) and os.path.exists(pre_push_file):
    # symlink already exists and is not broken
else:
    try: 
        os.symlink()
    except OSError, e:
        # os.symlink() fails when symlink already exists; must unlink first
        if e.errno == errno.EEXIST:
            os.unlink()
            os.symlink()
```
Once I figure out this CLA issue, I can definitely put in a PR for it.
Could you perhaps try cloning it again in a different (temporary) directory and see if it recurs? That might give a clue and might suggest the right changes to make to the start scripts.

Also I think the CLA issue is fixed, so please feel free to go ahead and take this up!
It didn't occur again. I checked the symlink immediately after git cloning, and it was intact. The original error with the broken symlink could have been due to:
- After cloning and before running any scripts, I changed my branch head from `@remote/develop` to `@upstream/develop` and made a hard sync and push since my branch was very outdated

Not sure how this could have broken the symlink, but @seanlip -- do you think we should still push a fix to the codebase even if it doesn't occur by default?
Yeah, I think so, as long as the fix can't possibly do any harm. Thanks @EricZLou! 
I'm getting the same error :
`Installing pre-commit hook for git
Symlink already exists
Making pre-commit hook file executable ...
pre-commit hook file is now executable!
Installing pre-push hook for git
Symlink already exists
Making pre-push hook file executable ...
Traceback (most recent call last):
  File "/usr/lib/python2.7/runpy.py", line 174, in _run_module_as_main
    "__main__", fname, loader, pkg_name)
  File "/usr/lib/python2.7/runpy.py", line 72, in _run_code
    exec code in run_globals
  File "/home/rajkumar/Documents/projects/oppia/scripts/start.py", line 33, in <module>
    install_third_party_libs.main()
  File "scripts/install_third_party_libs.py", line 225, in main
    pre_push_hook.main(args=['--install'])
  File "scripts/pre_push_hook.py", line 370, in main
    install_hook()
  File "scripts/pre_push_hook.py", line 322, in install_hook
    raise ValueError(err_chmod_cmd)
ValueError: chmod: cannot operate on dangling symlink '/home/rajkumar/Documents/projects/oppia/.git/hooks/pre-push'`


Reopening this because I am also getting this error. I'm looking into a fix now. @sudiptapradhan1181 @rajkumaar23 -- any workarounds that you remember?
Solution:
```
unlink /path/to/.../opensource/oppia/.git/hooks/pre-push
python -m scripts.start
```
Thanks @EricZLou! 

One question. Does this need to be added to our setup scripts? Why did it happen in the first place / how can we auto-detect and fix this so that it's not a problem for contributors in the future?
Hi @EricZLou, you need to sign the CLA before you can get assigned to issues. Follow the instructions <a href="https://github.com/oppia/oppia/wiki/Contributing-code-to-Oppia#setting-things-up">here</a> to get started. I am unassigning you for now, feel free to assign yourself once you have signed the CLA. Thanks!
I'm not sure why it happened in the first place as I was working off a completely clean oppia repo that I just cloned. But, we could always do some error checking in the `pre_push_hook.py` script like such:
Re: https://github.com/oppia/oppia/blob/develop/scripts/pre_push_hook.py#L334
```
if os.path.islink(pre_push_file) and os.path.exists(pre_push_file):
    # symlink already exists and is not broken
else:
    try: 
        os.symlink()
    except OSError, e:
        # os.symlink() fails when symlink already exists; must unlink first
        if e.errno == errno.EEXIST:
            os.unlink()
            os.symlink()
```
Once I figure out this CLA issue, I can definitely put in a PR for it.
Could you perhaps try cloning it again in a different (temporary) directory and see if it recurs? That might give a clue and might suggest the right changes to make to the start scripts.

Also I think the CLA issue is fixed, so please feel free to go ahead and take this up!
It didn't occur again. I checked the symlink immediately after git cloning, and it was intact. The original error with the broken symlink could have been due to:
- After cloning and before running any scripts, I changed my branch head from `@remote/develop` to `@upstream/develop` and made a hard sync and push since my branch was very outdated

Not sure how this could have broken the symlink, but @seanlip -- do you think we should still push a fix to the codebase even if it doesn't occur by default?
Yeah, I think so, as long as the fix can't possibly do any harm. Thanks @EricZLou! 

Resolving patch:
diff --git a/scripts/pre_commit_hook.py b/scripts/pre_commit_hook.py
--- a/scripts/pre_commit_hook.py
+++ b/scripts/pre_commit_hook.py
@@ -64,12 +64,18 @@ def install_hook():
     hooks_dir = os.path.join(oppia_dir, '.git', 'hooks')
     pre_commit_file = os.path.join(hooks_dir, 'pre-commit')
     chmod_cmd = ['chmod', '+x', pre_commit_file]
-    if os.path.islink(pre_commit_file):
+    file_is_symlink = os.path.islink(pre_commit_file)
+    file_exists = os.path.exists(pre_commit_file)
+    if file_is_symlink and file_exists:
         python_utils.PRINT('Symlink already exists')
     else:
         # This is needed, because otherwise some systems symlink/copy the .pyc
         # file instead of the .py file.
         this_file = __file__.replace('pyc', 'py')
+        # If its a broken symlink, delete it.
+        if file_is_symlink and not file_exists:
+            os.unlink(pre_commit_file)
+            python_utils.PRINT('Removing broken symlink')
         try:
             os.symlink(os.path.abspath(this_file), pre_commit_file)
             python_utils.PRINT('Created symlink in .git/hooks directory')
diff --git a/scripts/pre_push_hook.py b/scripts/pre_push_hook.py
--- a/scripts/pre_push_hook.py
+++ b/scripts/pre_push_hook.py
@@ -331,9 +331,15 @@ def install_hook():
     hooks_dir = os.path.join(oppia_dir, '.git', 'hooks')
     pre_push_file = os.path.join(hooks_dir, 'pre-push')
     chmod_cmd = ['chmod', '+x', pre_push_file]
-    if os.path.islink(pre_push_file):
+    file_is_symlink = os.path.islink(pre_push_file)
+    file_exists = os.path.exists(pre_push_file)
+    if file_is_symlink and file_exists:
         python_utils.PRINT('Symlink already exists')
     else:
+        # If its a broken symlink, delete it.
+        if file_is_symlink and not file_exists:
+            os.unlink(pre_push_file)
+            python_utils.PRINT('Removing broken symlink')
         try:
             os.symlink(os.path.abspath(__file__), pre_push_file)
             python_utils.PRINT('Created symlink in .git/hooks directory')


Test patch:
diff --git a/scripts/pre_commit_hook_test.py b/scripts/pre_commit_hook_test.py
--- a/scripts/pre_commit_hook_test.py
+++ b/scripts/pre_commit_hook_test.py
@@ -44,15 +44,18 @@ def mock_print(msg):
     def test_install_hook_with_existing_symlink(self):
         def mock_islink(unused_file):
             return True
+        def mock_exists(unused_file):
+            return True
         def mock_start_subprocess_for_result(unused_cmd_tokens):
             return ('Output', None)
 
         islink_swap = self.swap(os.path, 'islink', mock_islink)
+        exists_swap = self.swap(os.path, 'exists', mock_exists)
         subprocess_swap = self.swap(
             pre_commit_hook, 'start_subprocess_for_result',
             mock_start_subprocess_for_result)
 
-        with islink_swap, subprocess_swap, self.print_swap:
+        with islink_swap, exists_swap, subprocess_swap, self.print_swap:
             pre_commit_hook.install_hook()
         self.assertTrue('Symlink already exists' in self.print_arr)
         self.assertTrue(
@@ -61,15 +64,18 @@ def mock_start_subprocess_for_result(unused_cmd_tokens):
     def test_install_hook_with_error_in_making_pre_push_executable(self):
         def mock_islink(unused_file):
             return True
+        def mock_exists(unused_file):
+            return True
         def mock_start_subprocess_for_result(unused_cmd_tokens):
             return ('Output', 'Error')
 
         islink_swap = self.swap(os.path, 'islink', mock_islink)
+        exists_swap = self.swap(os.path, 'exists', mock_exists)
         subprocess_swap = self.swap(
             pre_commit_hook, 'start_subprocess_for_result',
             mock_start_subprocess_for_result)
 
-        with islink_swap, subprocess_swap, self.print_swap:
+        with islink_swap, exists_swap, subprocess_swap, self.print_swap:
             with self.assertRaisesRegexp(ValueError, 'Error'):
                 pre_commit_hook.install_hook()
         self.assertTrue('Symlink already exists' in self.print_arr)
@@ -82,18 +88,22 @@ def test_install_hook_with_creation_of_symlink(self):
         }
         def mock_islink(unused_file):
             return False
+        def mock_exists(unused_file):
+            return False
         def mock_start_subprocess_for_result(unused_cmd_tokens):
             return ('Output', None)
         def mock_symlink(unused_path, unused_file):
             check_function_calls['symlink_is_called'] = True
 
         islink_swap = self.swap(os.path, 'islink', mock_islink)
+        exists_swap = self.swap(os.path, 'exists', mock_exists)
         subprocess_swap = self.swap(
             pre_commit_hook, 'start_subprocess_for_result',
             mock_start_subprocess_for_result)
         symlink_swap = self.swap(os, 'symlink', mock_symlink)
 
-        with islink_swap, subprocess_swap, symlink_swap, self.print_swap:
+        with islink_swap, exists_swap, subprocess_swap, self.print_swap, (
+            symlink_swap):
             pre_commit_hook.install_hook()
         self.assertTrue(check_function_calls['symlink_is_called'])
         self.assertTrue(
@@ -112,6 +122,8 @@ def test_install_hook_with_error_in_creation_of_symlink(self):
         }
         def mock_islink(unused_file):
             return False
+        def mock_exists(unused_file):
+            return False
         def mock_start_subprocess_for_result(unused_cmd_tokens):
             return ('Output', None)
         def mock_symlink(unused_path, unused_file):
@@ -121,13 +133,14 @@ def mock_copy(unused_type, unused_file):
             check_function_calls['copy_is_called'] = True
 
         islink_swap = self.swap(os.path, 'islink', mock_islink)
+        exists_swap = self.swap(os.path, 'exists', mock_exists)
         subprocess_swap = self.swap(
             pre_commit_hook, 'start_subprocess_for_result',
             mock_start_subprocess_for_result)
         symlink_swap = self.swap(os, 'symlink', mock_symlink)
         copy_swap = self.swap(shutil, 'copy', mock_copy)
 
-        with islink_swap, subprocess_swap, symlink_swap, copy_swap:
+        with islink_swap, exists_swap, subprocess_swap, symlink_swap, copy_swap:
             with self.print_swap:
                 pre_commit_hook.install_hook()
         self.assertEqual(check_function_calls, expected_check_function_calls)
@@ -135,6 +148,40 @@ def mock_copy(unused_type, unused_file):
         self.assertTrue(
             'pre-commit hook file is now executable!' in self.print_arr)
 
+    def test_install_hook_with_broken_symlink(self):
+        check_function_calls = {
+            'unlink_is_called': False,
+            'symlink_is_called': False
+        }
+        def mock_islink(unused_file):
+            return True
+        def mock_exists(unused_file):
+            return False
+        def mock_start_subprocess_for_result(unused_cmd_tokens):
+            return ('Output', None)
+        def mock_unlink(unused_file):
+            check_function_calls['unlink_is_called'] = True
+        def mock_symlink(unused_path, unused_file):
+            check_function_calls['symlink_is_called'] = True
+
+        islink_swap = self.swap(os.path, 'islink', mock_islink)
+        exists_swap = self.swap(os.path, 'exists', mock_exists)
+        subprocess_swap = self.swap(
+            pre_commit_hook, 'start_subprocess_for_result',
+            mock_start_subprocess_for_result)
+        unlink_swap = self.swap(os, 'unlink', mock_unlink)
+        symlink_swap = self.swap(os, 'symlink', mock_symlink)
+
+        with islink_swap, exists_swap, subprocess_swap, self.print_swap:
+            with unlink_swap, symlink_swap:
+                pre_commit_hook.install_hook()
+        self.assertTrue(check_function_calls['unlink_is_called'])
+        self.assertTrue(check_function_calls['symlink_is_called'])
+        self.assertTrue('Removing broken symlink' in self.print_arr)
+        self.assertTrue(
+            'pre-commit hook file is now executable!'in self.print_arr)
+
+
     def test_start_subprocess_for_result(self):
         process = subprocess.Popen(
             ['echo', 'test'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
diff --git a/scripts/pre_push_hook_test.py b/scripts/pre_push_hook_test.py
--- a/scripts/pre_push_hook_test.py
+++ b/scripts/pre_push_hook_test.py
@@ -389,15 +389,18 @@ def mock_check_output(unused_cmd_tokens):
     def test_install_hook_with_existing_symlink(self):
         def mock_islink(unused_file):
             return True
+        def mock_exists(unused_file):
+            return True
         def mock_start_subprocess_for_result(unused_cmd_tokens):
             return ('Output', None)
 
         islink_swap = self.swap(os.path, 'islink', mock_islink)
+        exists_swap = self.swap(os.path, 'exists', mock_exists)
         subprocess_swap = self.swap(
             pre_push_hook, 'start_subprocess_for_result',
             mock_start_subprocess_for_result)
 
-        with islink_swap, subprocess_swap, self.print_swap:
+        with islink_swap, exists_swap, subprocess_swap, self.print_swap:
             pre_push_hook.install_hook()
         self.assertTrue('Symlink already exists' in self.print_arr)
         self.assertTrue(
@@ -406,15 +409,18 @@ def mock_start_subprocess_for_result(unused_cmd_tokens):
     def test_install_hook_with_error_in_making_pre_push_executable(self):
         def mock_islink(unused_file):
             return True
+        def mock_exists(unused_file):
+            return True
         def mock_start_subprocess_for_result(unused_cmd_tokens):
             return ('Output', 'Error')
 
         islink_swap = self.swap(os.path, 'islink', mock_islink)
+        exists_swap = self.swap(os.path, 'exists', mock_exists)
         subprocess_swap = self.swap(
             pre_push_hook, 'start_subprocess_for_result',
             mock_start_subprocess_for_result)
 
-        with islink_swap, subprocess_swap, self.print_swap:
+        with islink_swap, exists_swap, subprocess_swap, self.print_swap:
             with self.assertRaisesRegexp(ValueError, 'Error'):
                 pre_push_hook.install_hook()
         self.assertTrue('Symlink already exists' in self.print_arr)
@@ -427,18 +433,22 @@ def test_install_hook_with_creation_of_symlink(self):
         }
         def mock_islink(unused_file):
             return False
+        def mock_exists(unused_file):
+            return False
         def mock_start_subprocess_for_result(unused_cmd_tokens):
             return ('Output', None)
         def mock_symlink(unused_path, unused_file):
             check_function_calls['symlink_is_called'] = True
 
         islink_swap = self.swap(os.path, 'islink', mock_islink)
+        exists_swap = self.swap(os.path, 'exists', mock_exists)
         subprocess_swap = self.swap(
             pre_push_hook, 'start_subprocess_for_result',
             mock_start_subprocess_for_result)
         symlink_swap = self.swap(os, 'symlink', mock_symlink)
 
-        with islink_swap, subprocess_swap, symlink_swap, self.print_swap:
+        with islink_swap, exists_swap, subprocess_swap, symlink_swap, (
+            self.print_swap):
             pre_push_hook.install_hook()
         self.assertTrue(check_function_calls['symlink_is_called'])
         self.assertTrue(
@@ -457,6 +467,8 @@ def test_install_hook_with_error_in_creation_of_symlink(self):
         }
         def mock_islink(unused_file):
             return False
+        def mock_exists(unused_file):
+            return False
         def mock_start_subprocess_for_result(unused_cmd_tokens):
             return ('Output', None)
         def mock_symlink(unused_path, unused_file):
@@ -466,13 +478,14 @@ def mock_copy(unused_type, unused_file):
             check_function_calls['copy_is_called'] = True
 
         islink_swap = self.swap(os.path, 'islink', mock_islink)
+        exists_swap = self.swap(os.path, 'exists', mock_exists)
         subprocess_swap = self.swap(
             pre_push_hook, 'start_subprocess_for_result',
             mock_start_subprocess_for_result)
         symlink_swap = self.swap(os, 'symlink', mock_symlink)
         copy_swap = self.swap(shutil, 'copy', mock_copy)
 
-        with islink_swap, subprocess_swap, symlink_swap, copy_swap:
+        with islink_swap, exists_swap, subprocess_swap, symlink_swap, copy_swap:
             with self.print_swap:
                 pre_push_hook.install_hook()
         self.assertEqual(check_function_calls, expected_check_function_calls)
@@ -480,6 +493,39 @@ def mock_copy(unused_type, unused_file):
         self.assertTrue(
             'pre-push hook file is now executable!' in self.print_arr)
 
+    def test_install_hook_with_broken_symlink(self):
+        check_function_calls = {
+            'unlink_is_called': False,
+            'symlink_is_called': False
+        }
+        def mock_islink(unused_file):
+            return True
+        def mock_exists(unused_file):
+            return False
+        def mock_start_subprocess_for_result(unused_cmd_tokens):
+            return ('Output', None)
+        def mock_unlink(unused_file):
+            check_function_calls['unlink_is_called'] = True
+        def mock_symlink(unused_path, unused_file):
+            check_function_calls['symlink_is_called'] = True
+
+        islink_swap = self.swap(os.path, 'islink', mock_islink)
+        exists_swap = self.swap(os.path, 'exists', mock_exists)
+        subprocess_swap = self.swap(
+            pre_push_hook, 'start_subprocess_for_result',
+            mock_start_subprocess_for_result)
+        unlink_swap = self.swap(os, 'unlink', mock_unlink)
+        symlink_swap = self.swap(os, 'symlink', mock_symlink)
+
+        with islink_swap, exists_swap, subprocess_swap, self.print_swap:
+            with unlink_swap, symlink_swap:
+                pre_push_hook.install_hook()
+        self.assertTrue(check_function_calls['unlink_is_called'])
+        self.assertTrue(check_function_calls['symlink_is_called'])
+        self.assertTrue('Removing broken symlink' in self.print_arr)
+        self.assertTrue(
+            'pre-push hook file is now executable!'in self.print_arr)
+
     def test_does_diff_include_js_or_ts_files_with_js_file(self):
         self.assertTrue(
             pre_push_hook.does_diff_include_js_or_ts_files(