Repository: sphinx-doc/sphinx

Issue:
Missing remote image warnings not showing up when building HTML

<!--
  Important: This is a list of issues for Sphinx, not a forum.
  If you'd like to post a question, please move to sphinx-users group.
  https://groups.google.com/forum/#!forum/sphinx-users

  Thanks,
-->

### Problem
`make clean latexpdf` or `make clean latex` gives warning messages about missing images in the step "Resolving references", but `make clean html` does not. I don't even see a Resolving References step when doing `make clean html`

#### Procedure to reproduce the problem

`make clean latex`  shows warnings on missing remote images, e.g. 
```
WARNING: Could not fetch remote image: https://raw.githubusercontent.com/.../dashboard_redshift.png [404]

```
but `make clean html` does not. So my live site now shows a number of broken images.

#### Error logs / results

https://docs.signalfx.com/en/latest/integrations/integrations-reference/integrations.amazon.redshift.html

#### Expected results

Show warnings when building HTML

### Reproducible project / your project
- <link to your project, or attach zipped small project sample>

### Environment info
- OS: Mac 10.13.4
- Python version: 2.7
- Sphinx version: 1.7.5
- <Extra tools e.g.: Browser, tex or something else>


Discussion:

Patch:
diff --git a/CHANGES b/CHANGES
index e266c50da..91da1dea2 100644
--- a/CHANGES
+++ b/CHANGES
@@ -176,6 +176,7 @@ Features added
 * #6016: HTML search: A placeholder for the search summary prevents search
   result links from changing their position when the search terminates.  This
   makes navigating search results easier.
+* #5196: linkcheck also checks remote images exist
   
 Bugs fixed
 ----------
diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py
index 95892dbaf..1161edb9d 100644
--- a/sphinx/builders/linkcheck.py
+++ b/sphinx/builders/linkcheck.py
@@ -25,7 +25,7 @@
 from sphinx.util.console import (  # type: ignore
     purple, red, darkgreen, darkgray, darkred, turquoise
 )
-from sphinx.util.nodes import traverse_parent
+from sphinx.util.nodes import get_node_line
 from sphinx.util.requests import is_ssl_error
 
 if False:
@@ -271,17 +271,24 @@ def write_doc(self, docname, doctree):
         # type: (str, nodes.Node) -> None
         logger.info('')
         n = 0
-        for node in doctree.traverse(nodes.reference):
-            if 'refuri' not in node:
+
+        # reference nodes
+        for refnode in doctree.traverse(nodes.reference):
+            if 'refuri' not in refnode:
                 continue
-            uri = node['refuri']
-            lineno = None
-            for parent in traverse_parent(node):
-                if parent.line:
-                    lineno = parent.line
-                    break
+            uri = refnode['refuri']
+            lineno = get_node_line(refnode)
             self.wqueue.put((uri, docname, lineno), False)
             n += 1
+
+        # image nodes
+        for imgnode in doctree.traverse(nodes.image):
+            uri = imgnode['candidates'].get('?')
+            if uri and '://' in uri:
+                lineno = get_node_line(imgnode)
+                self.wqueue.put((uri, docname, lineno), False)
+                n += 1
+
         done = 0
         while done < n:
             self.process_result(self.rqueue.get())
diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py
index 484616b03..7870438f7 100644
--- a/sphinx/util/nodes.py
+++ b/sphinx/util/nodes.py
@@ -285,6 +285,14 @@ def find_source_node(node):
     return None
 
 
+def get_node_line(node):
+    # type: (nodes.Element) -> int
+    for pnode in traverse_parent(node):
+        if pnode.line:
+            return pnode.line
+    return None
+
+
 def traverse_parent(node, cls=None):
     # type: (nodes.Element, Any) -> Iterable[nodes.Element]
     while node:


Test patch:
diff --git a/tests/roots/test-linkcheck/links.txt b/tests/roots/test-linkcheck/links.txt
index d5057e08e..ac5ed3246 100644
--- a/tests/roots/test-linkcheck/links.txt
+++ b/tests/roots/test-linkcheck/links.txt
@@ -11,3 +11,6 @@ Some additional anchors to exercise ignore code
 * `Example Bar invalid <http://example.com/#top>`_
 * `Example anchor invalid <http://www.sphinx-doc.org/en/1.7/intro.html#does-not-exist>`_
 * `Complete nonsense <https://localhost:7777/doesnotexist>`_
+
+.. image:: http://example.com/image.png
+.. figure:: http://example.com/image2.png
diff --git a/tests/test_build_linkcheck.py b/tests/test_build_linkcheck.py
index 6e50bd758..6d25058eb 100644
--- a/tests/test_build_linkcheck.py
+++ b/tests/test_build_linkcheck.py
@@ -24,7 +24,10 @@ def test_defaults(app, status, warning):
     assert "Anchor 'does-not-exist' not found" in content
     # looking for non-existent URL should fail
     assert " Max retries exceeded with url: /doesnotexist" in content
-    assert len(content.splitlines()) == 3
+    # images should fail
+    assert "Not Found for url: http://example.com/image.png" in content
+    assert "Not Found for url: http://example.com/image2.png" in content
+    assert len(content.splitlines()) == 5
 
 
 @pytest.mark.sphinx(
@@ -32,7 +35,9 @@ def test_defaults(app, status, warning):
     confoverrides={'linkcheck_anchors_ignore': ["^!", "^top$"],
                    'linkcheck_ignore': [
                        'https://localhost:7777/doesnotexist',
-                       'http://www.sphinx-doc.org/en/1.7/intro.html#']
+                       'http://www.sphinx-doc.org/en/1.7/intro.html#',
+                       'http://example.com/image.png',
+                       'http://example.com/image2.png']
                    })
 def test_anchors_ignored(app, status, warning):
     app.builder.build_all()