Completed the patch for Bug #215126.
* Fixes an incorrect variable in a PyDict_CheckExact.
* Allow general mapping locals arguments for the execfile() function
  and exec statement.
* Add tests.
diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py
index fdbbdfc..6654f56 100644
--- a/Lib/test/test_builtin.py
+++ b/Lib/test/test_builtin.py
@@ -282,6 +282,11 @@
         self.assertEqual(eval('globals()', g, m), g)
         self.assertEqual(eval('locals()', g, m), m)
         self.assertRaises(TypeError, eval, 'a', m)
+        class A:
+            "Non-mapping"
+            pass
+        m = A()
+        self.assertRaises(TypeError, eval, 'a', g, m)
 
         # Verify that dict subclasses work as well
         class D(dict):
@@ -336,6 +341,26 @@
         locals['z'] = 0
         execfile(TESTFN, globals, locals)
         self.assertEqual(locals['z'], 2)
+
+        class M:
+            "Test mapping interface versus possible calls from execfile()."
+            def __init__(self):
+                self.z = 10
+            def __getitem__(self, key):
+                if key == 'z':
+                    return self.z
+                raise KeyError
+            def __setitem__(self, key, value):
+                if key == 'z':
+                    self.z = value
+                    return
+                raise KeyError
+
+        locals = M()
+        locals['z'] = 0
+        execfile(TESTFN, globals, locals)
+        self.assertEqual(locals['z'], 2)
+
         unlink(TESTFN)
         self.assertRaises(TypeError, execfile)
         import os
diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py
index 5b7b717..b1644cb 100644
--- a/Lib/test/test_compile.py
+++ b/Lib/test/test_compile.py
@@ -44,6 +44,63 @@
         except SyntaxError:
             pass
 
+    def test_exec_with_general_mapping_for_locals(self):
+
+        class M:
+            "Test mapping interface versus possible calls from eval()."
+            def __getitem__(self, key):
+                if key == 'a':
+                    return 12
+                raise KeyError
+            def __setitem__(self, key, value):
+                self.results = (key, value)
+            def keys(self):
+                return list('xyz')
+
+        m = M()
+        g = globals()
+        exec 'z = a' in g, m
+        self.assertEqual(m.results, ('z', 12))
+        try:
+            exec 'z = b' in g, m
+        except NameError:
+            pass
+        else:
+            self.fail('Did not detect a KeyError')
+        exec 'z = dir()' in g, m
+        self.assertEqual(m.results, ('z', list('xyz')))
+        exec 'z = globals()' in g, m
+        self.assertEqual(m.results, ('z', g))
+        exec 'z = locals()' in g, m
+        self.assertEqual(m.results, ('z', m))
+        try:
+            exec 'z = b' in m
+        except TypeError:
+            pass
+        else:
+            self.fail('Did not validate globals as a real dict')
+
+        class A:
+            "Non-mapping"
+            pass
+        m = A()
+        try:
+            exec 'z = a' in g, m
+        except TypeError:
+            pass
+        else:
+            self.fail('Did not validate locals as a mapping')
+
+        # Verify that dict subclasses work as well
+        class D(dict):
+            def __getitem__(self, key):
+                if key == 'a':
+                    return 12
+                return dict.__getitem__(self, key)
+        d = D()
+        exec 'z = a' in g, d
+        self.assertEqual(d['z'], 12)
+
     def test_complex_args(self):
 
         def comp_args((a, b)):