~ubuntu-branches/ubuntu/maverick/twisted-web/maverick

« back to all changes in this revision

Viewing changes to twisted/web/test/test_wsgi.py

  • Committer: Bazaar Package Importer
  • Author(s): Free Ekanayaka
  • Date: 2010-03-09 14:14:09 UTC
  • mfrom: (0.2.8 upstream)
  • Revision ID: james.westby@ubuntu.com-20100309141409-49b1kjx0hd07aws1
Tags: 10.0.0-1
* New upstream release
* Add myself to Uploaders:
* Bump standards version

Show diffs side-by-side

added added

removed removed

Lines of Context:
7
7
 
8
8
__metaclass__ = type
9
9
 
10
 
import tempfile
11
 
 
12
10
from sys import exc_info
13
11
from urllib import quote
14
 
from StringIO import StringIO
15
12
from thread import get_ident
 
13
import StringIO, cStringIO, tempfile
16
14
 
17
15
from zope.interface.verify import verifyObject
18
16
 
121
119
    def lowLevelRender(
122
120
        self, requestFactory, applicationFactory, channelFactory, method,
123
121
        version, resourceSegments, requestSegments, query=None, headers=[],
124
 
        body=None, safe='', useTempfile=False):
 
122
        body=None, safe=''):
125
123
        """
126
124
        @param method: A C{str} giving the request method to use.
127
125
 
144
142
        @param safe: A C{str} giving the bytes which are to be considered
145
143
            I{safe} for inclusion in the request URI and not quoted.
146
144
 
147
 
        @param useTempfile: A boolean flag indicating that an instance of
148
 
            L{tempfile.TemporaryFile} should be used to expose the the request
149
 
            content rather than a L{StringIO.StringIO}.
150
 
 
151
145
        @return: A L{Deferred} which will be called back with a two-tuple of
152
146
            the arguments passed which would be passed to the WSGI application
153
147
            object for this configuration and request (ie, the environment and
168
162
            request.requestHeaders.addRawHeader(k, v)
169
163
        request.gotLength(0)
170
164
        if body:
171
 
            if useTempfile:
172
 
                request.content = tempfile.TemporaryFile()
173
 
                request.content.write(body)
174
 
                request.content.seek(0)
175
 
            else:
176
 
                request.content = StringIO(body)
 
165
            request.content.write(body)
 
166
            request.content.seek(0)
177
167
        uri = '/' + '/'.join([quote(seg, safe) for seg in requestSegments])
178
168
        if query is not None:
179
169
            uri += '?' + '&'.join(['='.join([quote(k, safe), quote(v, safe)])
338
328
        present.addCallback(self.environKeyEqual('QUERY_STRING', 'foo=bar'))
339
329
 
340
330
        unencoded = self.render('GET', '1.1', [], [''], [('/', '/')])
341
 
        unencoded.addCallback(self.environKeyEqual('QUERY_STRING', '/=/'))
 
331
        unencoded.addCallback(self.environKeyEqual('QUERY_STRING', '%2F=%2F'))
342
332
 
343
333
        # "?" is reserved in the <searchpart> portion of a URL.  However, it
344
334
        # seems to be a common mistake of clients to forget to quote it.  So,
481
471
        application has the value C{(1, 0)} indicating that this is a WSGI 1.0
482
472
        container.
483
473
        """
484
 
        version = self.render('GET', '1.1', [], [''])
485
 
        version.addCallback(self.environKeyEqual('wsgi.version', (1, 0)))
486
 
        return version
 
474
        versionDeferred = self.render('GET', '1.1', [], [''])
 
475
        versionDeferred.addCallback(self.environKeyEqual('wsgi.version', (1, 0)))
 
476
        return versionDeferred
487
477
 
488
478
 
489
479
    def test_wsgiRunOnce(self):
529
519
            return channel
530
520
 
531
521
        self.channelFactory = DummyChannel
532
 
        http = self.render('GET', '1.1', [], [''])
533
 
        http.addCallback(self.environKeyEqual('wsgi.url_scheme', 'http'))
 
522
        httpDeferred = self.render('GET', '1.1', [], [''])
 
523
        httpDeferred.addCallback(self.environKeyEqual('wsgi.url_scheme', 'http'))
534
524
 
535
525
        self.channelFactory = channelFactory
536
 
        https = self.render('GET', '1.1', [], [''])
537
 
        https.addCallback(self.environKeyEqual('wsgi.url_scheme', 'https'))
 
526
        httpsDeferred = self.render('GET', '1.1', [], [''])
 
527
        httpsDeferred.addCallback(self.environKeyEqual('wsgi.url_scheme', 'https'))
538
528
 
539
 
        return gatherResults([http, https])
 
529
        return gatherResults([httpDeferred, httpsDeferred])
540
530
 
541
531
 
542
532
    def test_wsgiErrors(self):
568
558
        return errors
569
559
 
570
560
 
571
 
    def test_wsgiInput(self):
572
 
        """
573
 
        The C{'wsgi.input'} key of the C{environ} C{dict} passed to the
574
 
        application is a file-like object (as defined in the U{Input and Errors
575
 
        Streams<http://www.python.org/dev/peps/pep-0333/#input-and-error-streams>}
576
 
        section of PEP 333) which makes the request body available to the
577
 
        application.
578
 
        """
 
561
class InputStreamTestMixin(WSGITestsMixin):
 
562
    """
 
563
    A mixin for L{TestCase} subclasses which defines a number of tests against
 
564
    L{_InputStream}.  The subclass is expected to create a file-like object to
 
565
    be wrapped by an L{_InputStream} under test.
 
566
    """
 
567
    def getFileType(self):
 
568
        raise NotImplementedError(
 
569
            "%s.getFile must be implemented" % (self.__class__.__name__,))
 
570
 
 
571
 
 
572
    def _renderAndReturnReaderResult(self, reader, content):
 
573
        contentType = self.getFileType()
 
574
        class CustomizedRequest(Request):
 
575
            def gotLength(self, length):
 
576
                # Always allocate a file of the specified type, instead of
 
577
                # using the base behavior of selecting one depending on the
 
578
                # length.
 
579
                self.content = contentType()
 
580
 
579
581
        def appFactoryFactory(reader):
580
582
            result = Deferred()
581
583
            def applicationFactory():
586
588
                    return iter(())
587
589
                return application
588
590
            return result, applicationFactory
589
 
 
590
 
        inputRead, appFactory = appFactoryFactory(
591
 
            lambda input: [input.read(1), input.read()])
592
 
        self.lowLevelRender(
593
 
            Request, appFactory, DummyChannel,
594
 
            'GET', '1.1', [], [''], None, [],
595
 
            "hello, world\n"
596
 
            "how are you\n")
597
 
        inputRead.addCallback(
598
 
            self.assertEqual, ['h', 'ello, world\nhow are you\n'])
599
 
 
600
 
        # COMPATIBILITY NOTE: the size argument is excluded from the WSGI
601
 
        # specification, but is provided here anyhow, because useful libraries
602
 
        # such as python stdlib's cgi.py assume their input file-like-object
603
 
        # supports readline with a size argument. If you use it, be aware your
604
 
        # application may not be portable to other conformant WSGI servers.
605
 
        inputReadline, appFactory = appFactoryFactory(
606
 
            lambda input: [input.readline(), input.readline(None),
607
 
                           input.readline(-1), input.readline(20),
608
 
                           input.readline(5), input.readline(5),
609
 
                           input.readline(5)])
610
 
        self.lowLevelRender(
611
 
            Request, appFactory, DummyChannel,
612
 
            'GET', '1.1', [], [''], None, [],
613
 
            "hello, world\n"
614
 
            "how are you\n"
615
 
            "I am great\n"
616
 
            "goodbye now\n"
617
 
            "no data here\n")
618
 
        inputReadline.addCallback(
619
 
            self.assertEqual, [
620
 
                'hello, world\n', 'how are you\n', 'I am great\n',
621
 
                'goodbye now\n', 'no da', 'ta he', 're\n'])
622
 
       
623
 
        inputReadlineNonePOST, appFactory = appFactoryFactory(
624
 
            lambda input: [input.readline(), input.readline(-1),
625
 
                           input.readline(None), input.readline(),
626
 
                           input.readline(-1)])
627
 
        
628
 
        self.lowLevelRender(
629
 
            Request, appFactory, DummyChannel,
630
 
            'POST', '1.1', [], [''], None, [
631
 
                ('Content-Type', 'multipart/form-data; boundary=---------------------------168072824752491622650073'),
632
 
                ('Content-Length', '130'),
633
 
            ],
634
 
            "\n".join((["-----------------------------168072824752491622650073\n"
635
 
            "Content-Disposition: form-data; name=\"search-query\"\n\n"
636
 
            "this-is-my-search-query\n"])),
637
 
            useTempfile=True)
638
 
        inputReadlineNonePOST.addCallback(
639
 
            self.assertEqual, [
640
 
                '-----------------------------168072824752491622650073\n',
641
 
                'Content-Disposition: form-data; name="search-query"\n',
642
 
                '\n',
643
 
                'this-is-my-search-query\n',
644
 
                ''
645
 
            ])
646
 
        
647
 
        inputReadlinesNoArg, appFactory = appFactoryFactory(
648
 
            lambda input: input.readlines())
649
 
        self.lowLevelRender(
650
 
            Request, appFactory, DummyChannel,
651
 
            'GET', '1.1', [], [''], None, [],
652
 
            "foo\n"
653
 
            "bar\n")
654
 
        inputReadlinesNoArg.addCallback(
655
 
            self.assertEqual, ["foo\n", "bar\n"])
656
 
 
657
 
 
658
 
        inputReadlinesNone, appFactory = appFactoryFactory(
659
 
            lambda input: input.readlines(None))
660
 
        self.lowLevelRender(
661
 
            Request, appFactory, DummyChannel,
662
 
            'GET', '1.1', [], [''], None, [],
663
 
            "foo\n"
664
 
            "bar\n")
665
 
        inputReadlinesNone.addCallback(
666
 
            self.assertEqual, ["foo\n", "bar\n"])
667
 
 
668
 
 
669
 
        inputReadlinesLength, appFactory = appFactoryFactory(
670
 
            lambda input: input.readlines(6))
671
 
        self.lowLevelRender(
672
 
            Request, appFactory, DummyChannel,
673
 
            'GET', '1.1', [], [''], None, [],
674
 
            "foo\n"
675
 
            "bar\n")
676
 
        inputReadlinesLength.addCallback(
677
 
            self.assertEqual, ["foo\n", "bar\n"])
678
 
 
679
 
 
680
 
        inputIter, appFactory = appFactoryFactory(
681
 
            lambda input: list(input))
682
 
        self.lowLevelRender(
683
 
            Request, appFactory, DummyChannel,
684
 
            'GET', '1.1', [], [''], None, [],
685
 
            'foo\n'
686
 
            'bar\n')
687
 
        inputIter.addCallback(
688
 
            self.assertEqual, ['foo\n', 'bar\n'])
689
 
 
690
 
        return gatherResults([
691
 
                inputRead, inputReadline, inputReadlineNonePOST,
692
 
                inputReadlinesNoArg, inputReadlinesNone, inputReadlinesLength,
693
 
                inputIter])
 
591
        d, appFactory = appFactoryFactory(reader)
 
592
        self.lowLevelRender(
 
593
            CustomizedRequest, appFactory, DummyChannel,
 
594
            'PUT', '1.1', [], [''], None, [],
 
595
            content)
 
596
        return d
 
597
 
 
598
 
 
599
    def test_readAll(self):
 
600
        """
 
601
        Calling L{_InputStream.read} with no arguments returns the entire input
 
602
        stream.
 
603
        """
 
604
        bytes = "some bytes are here"
 
605
        d = self._renderAndReturnReaderResult(lambda input: input.read(), bytes)
 
606
        d.addCallback(self.assertEquals, bytes)
 
607
        return d
 
608
 
 
609
 
 
610
    def test_readSome(self):
 
611
        """
 
612
        Calling L{_InputStream.read} with an integer returns that many bytes
 
613
        from the input stream, as long as it is less than or equal to the total
 
614
        number of bytes available.
 
615
        """
 
616
        bytes = "hello, world."
 
617
        d = self._renderAndReturnReaderResult(lambda input: input.read(3), bytes)
 
618
        d.addCallback(self.assertEquals, "hel")
 
619
        return d
 
620
 
 
621
 
 
622
    def test_readMoreThan(self):
 
623
        """
 
624
        Calling L{_InputStream.read} with an integer that is greater than the
 
625
        total number of bytes in the input stream returns all bytes in the
 
626
        input stream.
 
627
        """
 
628
        bytes = "some bytes are here"
 
629
        d = self._renderAndReturnReaderResult(
 
630
            lambda input: input.read(len(bytes) + 3), bytes)
 
631
        d.addCallback(self.assertEquals, bytes)
 
632
        return d
 
633
 
 
634
 
 
635
    def test_readTwice(self):
 
636
        """
 
637
        Calling L{_InputStream.read} a second time returns bytes starting from
 
638
        the position after the last byte returned by the previous read.
 
639
        """
 
640
        bytes = "some bytes, hello"
 
641
        def read(input):
 
642
            input.read(3)
 
643
            return input.read()
 
644
        d = self._renderAndReturnReaderResult(read, bytes)
 
645
        d.addCallback(self.assertEquals, bytes[3:])
 
646
        return d
 
647
 
 
648
 
 
649
    def test_readNone(self):
 
650
        """
 
651
        Calling L{_InputStream.read} with C{None} as an argument returns all
 
652
        bytes in the input stream.
 
653
        """
 
654
        bytes = "the entire stream"
 
655
        d = self._renderAndReturnReaderResult(
 
656
            lambda input: input.read(None), bytes)
 
657
        d.addCallback(self.assertEquals, bytes)
 
658
        return d
 
659
 
 
660
 
 
661
    def test_readNegative(self):
 
662
        """
 
663
        Calling L{_InputStream.read} with a negative integer as an argument
 
664
        returns all bytes in the input stream.
 
665
        """
 
666
        bytes = "all of the input"
 
667
        d = self._renderAndReturnReaderResult(
 
668
            lambda input: input.read(-1), bytes)
 
669
        d.addCallback(self.assertEquals, bytes)
 
670
        return d
 
671
 
 
672
 
 
673
    def test_readline(self):
 
674
        """
 
675
        Calling L{_InputStream.readline} with no argument returns one line from
 
676
        the input stream.
 
677
        """
 
678
        bytes = "hello\nworld"
 
679
        d = self._renderAndReturnReaderResult(
 
680
            lambda input: input.readline(), bytes)
 
681
        d.addCallback(self.assertEquals, "hello\n")
 
682
        return d
 
683
 
 
684
 
 
685
    def test_readlineSome(self):
 
686
        """
 
687
        Calling L{_InputStream.readline} with an integer returns at most that
 
688
        many bytes, even if it is not enough to make up a complete line.
 
689
 
 
690
        COMPATIBILITY NOTE: the size argument is excluded from the WSGI
 
691
        specification, but is provided here anyhow, because useful libraries
 
692
        such as python stdlib's cgi.py assume their input file-like-object
 
693
        supports readline with a size argument. If you use it, be aware your
 
694
        application may not be portable to other conformant WSGI servers.
 
695
        """
 
696
        bytes = "goodbye\nworld"
 
697
        d = self._renderAndReturnReaderResult(
 
698
            lambda input: input.readline(3), bytes)
 
699
        d.addCallback(self.assertEquals, "goo")
 
700
        return d
 
701
 
 
702
 
 
703
    def test_readlineMoreThan(self):
 
704
        """
 
705
        Calling L{_InputStream.readline} with an integer which is greater than
 
706
        the number of bytes in the next line returns only the next line.
 
707
        """
 
708
        bytes = "some lines\nof text"
 
709
        d = self._renderAndReturnReaderResult(
 
710
            lambda input: input.readline(20), bytes)
 
711
        d.addCallback(self.assertEquals, "some lines\n")
 
712
        return d
 
713
 
 
714
 
 
715
    def test_readlineTwice(self):
 
716
        """
 
717
        Calling L{_InputStream.readline} a second time returns the line
 
718
        following the line returned by the first call.
 
719
        """
 
720
        bytes = "first line\nsecond line\nlast line"
 
721
        def readline(input):
 
722
            input.readline()
 
723
            return input.readline()
 
724
        d = self._renderAndReturnReaderResult(readline, bytes)
 
725
        d.addCallback(self.assertEquals, "second line\n")
 
726
        return d
 
727
 
 
728
 
 
729
    def test_readlineNone(self):
 
730
        """
 
731
        Calling L{_InputStream.readline} with C{None} as an argument returns
 
732
        one line from the input stream.
 
733
        """
 
734
        bytes = "this is one line\nthis is another line"
 
735
        d = self._renderAndReturnReaderResult(
 
736
            lambda input: input.readline(None), bytes)
 
737
        d.addCallback(self.assertEquals, "this is one line\n")
 
738
        return d
 
739
 
 
740
 
 
741
    def test_readlineNegative(self):
 
742
        """
 
743
        Calling L{_InputStream.readline} with a negative integer as an argument
 
744
        returns one line from the input stream.
 
745
        """
 
746
        bytes = "input stream line one\nline two"
 
747
        d = self._renderAndReturnReaderResult(
 
748
            lambda input: input.readline(-1), bytes)
 
749
        d.addCallback(self.assertEquals, "input stream line one\n")
 
750
        return d
 
751
 
 
752
 
 
753
    def test_readlines(self):
 
754
        """
 
755
        Calling L{_InputStream.readlines} with no arguments returns a list of
 
756
        all lines from the input stream.
 
757
        """
 
758
        bytes = "alice\nbob\ncarol"
 
759
        d = self._renderAndReturnReaderResult(
 
760
            lambda input: input.readlines(), bytes)
 
761
        d.addCallback(self.assertEquals, ["alice\n", "bob\n", "carol"])
 
762
        return d
 
763
 
 
764
 
 
765
    def test_readlinesSome(self):
 
766
        """
 
767
        Calling L{_InputStream.readlines} with an integer as an argument
 
768
        returns a list of lines from the input stream with the argument serving
 
769
        as an approximate bound on the total number of bytes to read.
 
770
        """
 
771
        bytes = "123\n456\n789\n0"
 
772
        d = self._renderAndReturnReaderResult(
 
773
            lambda input: input.readlines(5), bytes)
 
774
        def cbLines(lines):
 
775
            # Make sure we got enough lines to make 5 bytes.  Anything beyond
 
776
            # that is fine too.
 
777
            self.assertEquals(lines[:2], ["123\n", "456\n"])
 
778
        d.addCallback(cbLines)
 
779
        return d
 
780
 
 
781
 
 
782
    def test_readlinesMoreThan(self):
 
783
        """
 
784
        Calling L{_InputStream.readlines} with an integer which is greater than
 
785
        the total number of bytes in the input stream returns a list of all
 
786
        lines from the input.
 
787
        """
 
788
        bytes = "one potato\ntwo potato\nthree potato"
 
789
        d = self._renderAndReturnReaderResult(
 
790
            lambda input: input.readlines(100), bytes)
 
791
        d.addCallback(
 
792
            self.assertEquals,
 
793
            ["one potato\n", "two potato\n", "three potato"])
 
794
        return d
 
795
 
 
796
 
 
797
    def test_readlinesAfterRead(self):
 
798
        """
 
799
        Calling L{_InputStream.readlines} after a call to L{_InputStream.read}
 
800
        returns lines starting at the byte after the last byte returned by the
 
801
        C{read} call.
 
802
        """
 
803
        bytes = "hello\nworld\nfoo"
 
804
        def readlines(input):
 
805
            input.read(7)
 
806
            return input.readlines()
 
807
        d = self._renderAndReturnReaderResult(readlines, bytes)
 
808
        d.addCallback(self.assertEquals, ["orld\n", "foo"])
 
809
        return d
 
810
 
 
811
 
 
812
    def test_readlinesNone(self):
 
813
        """
 
814
        Calling L{_InputStream.readlines} with C{None} as an argument returns
 
815
        all lines from the input.
 
816
        """
 
817
        bytes = "one fish\ntwo fish\n"
 
818
        d = self._renderAndReturnReaderResult(
 
819
            lambda input: input.readlines(None), bytes)
 
820
        d.addCallback(self.assertEquals, ["one fish\n", "two fish\n"])
 
821
        return d
 
822
 
 
823
 
 
824
    def test_readlinesNegative(self):
 
825
        """
 
826
        Calling L{_InputStream.readlines} with a negative integer as an
 
827
        argument returns a list of all lines from the input.
 
828
        """
 
829
        bytes = "red fish\nblue fish\n"
 
830
        d = self._renderAndReturnReaderResult(
 
831
            lambda input: input.readlines(-1), bytes)
 
832
        d.addCallback(self.assertEquals, ["red fish\n", "blue fish\n"])
 
833
        return d
 
834
 
 
835
 
 
836
    def test_iterable(self):
 
837
        """
 
838
        Iterating over L{_InputStream} produces lines from the input stream.
 
839
        """
 
840
        bytes = "green eggs\nand ham\n"
 
841
        d = self._renderAndReturnReaderResult(lambda input: list(input), bytes)
 
842
        d.addCallback(self.assertEquals, ["green eggs\n", "and ham\n"])
 
843
        return d
 
844
 
 
845
 
 
846
    def test_iterableAfterRead(self):
 
847
        """
 
848
        Iterating over L{_InputStream} after calling L{_InputStream.read}
 
849
        produces lines from the input stream starting from the first byte after
 
850
        the last byte returned by the C{read} call.
 
851
        """
 
852
        bytes = "green eggs\nand ham\n"
 
853
        def iterate(input):
 
854
            input.read(3)
 
855
            return list(input)
 
856
        d = self._renderAndReturnReaderResult(iterate, bytes)
 
857
        d.addCallback(self.assertEquals, ["en eggs\n", "and ham\n"])
 
858
        return d
 
859
 
 
860
 
 
861
 
 
862
class InputStreamStringIOTests(InputStreamTestMixin, TestCase):
 
863
    """
 
864
    Tests for L{_InputStream} when it is wrapped around a L{StringIO.StringIO}.
 
865
    """
 
866
    def getFileType(self):
 
867
        return StringIO.StringIO
 
868
 
 
869
 
 
870
 
 
871
class InputStreamCStringIOTests(InputStreamTestMixin, TestCase):
 
872
    """
 
873
    Tests for L{_InputStream} when it is wrapped around a
 
874
    L{cStringIO.StringIO}.
 
875
    """
 
876
    def getFileType(self):
 
877
        return cStringIO.StringIO
 
878
 
 
879
 
 
880
 
 
881
class InputStreamTemporaryFileTests(InputStreamTestMixin, TestCase):
 
882
    """
 
883
    Tests for L{_InputStream} when it is wrapped around a L{tempfile.TemporaryFile}.
 
884
    """
 
885
    def getFileType(self):
 
886
        return tempfile.TemporaryFile
694
887
 
695
888
 
696
889