1#!/usr/bin/python
2#
3# Copyright 2015 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7import codecs
8import copy
9import credits_updater as cu
10import os
11import string
12import unittest
13
14# Assumes this script is in ffmpeg/chromium/scripts/
15SOURCE_DIR = os.path.join(
16    os.path.dirname(os.path.abspath(__file__)), os.path.pardir, os.path.pardir)
17OUTPUT_FILE = 'CREDITS.testing'
18
19# Expected credits for swresample.h applied with the rot13 encoding. Otherwise
20# license scanners get confused about the license of this file.
21SWRESAMPLE_H_LICENSE_ROT_13 = """yvofjerfnzcyr/fjerfnzcyr.u
22
23Pbclevtug (P) 2011-2013 Zvpunry Avrqreznlre (zvpunryav@tzk.ng)
24
25Guvf svyr vf cneg bs yvofjerfnzcyr
26
27yvofjerfnzcyr vf serr fbsgjner; lbh pna erqvfgevohgr vg naq/be
28zbqvsl vg haqre gur grezf bs gur TAH Yrffre Trareny Choyvp
29Yvprafr nf choyvfurq ol gur Serr Fbsgjner Sbhaqngvba; rvgure
30irefvba 2.1 bs gur Yvprafr, be (ng lbhe bcgvba) nal yngre irefvba.
31
32yvofjerfnzcyr vf qvfgevohgrq va gur ubcr gung vg jvyy or hfrshy,
33ohg JVGUBHG NAL JNEENAGL; jvgubhg rira gur vzcyvrq jneenagl bs
34ZREPUNAGNOVYVGL be SVGARFF SBE N CNEGVPHYNE CHECBFR.  Frr gur TAH
35Yrffre Trareny Choyvp Yvprafr sbe zber qrgnvyf.
36
37Lbh fubhyq unir erprvirq n pbcl bs gur TAH Yrffre Trareny Choyvp
38Yvprafr nybat jvgu yvofjerfnzcyr; vs abg, jevgr gb gur Serr Fbsgjner
39Sbhaqngvba, Vap., 51 Senaxyva Fgerrg, Svsgu Sybbe, Obfgba, ZN 02110-1301 HFN"""
40
41# The real expected credits for swresample.h.
42SWRESAMPLE_H_LICENSE = codecs.decode(SWRESAMPLE_H_LICENSE_ROT_13, 'rot13')
43
44
45def NewCreditsUpdater():
46  return cu.CreditsUpdater(SOURCE_DIR, OUTPUT_FILE)
47
48
49class CreditsUpdaterUnittest(unittest.TestCase):
50
51  def tearDown(self):
52    # Cleanup the testing output file
53    test_credits = os.path.join(SOURCE_DIR, OUTPUT_FILE)
54    if os.path.exists(test_credits):
55      os.remove(test_credits)
56
57  def testNoFiles(self):
58    # Write credits without processing any files.
59    NewCreditsUpdater().WriteCredits()
60
61    # Credits should *always* have LICENSE.md followed by full LGPL text.
62    expected_lines = NormalizeNewLines(GetLicenseMdLines() +
63                                       GetSeparatorLines() +
64                                       GetLicenseLines(cu.License.LGPL))
65    credits_lines = ReadCreditsLines()
66    self.assertEqual(expected_lines, credits_lines)
67
68  def testLPGLFiles(self):
69    # Process two known LGPL files
70    updater = NewCreditsUpdater()
71    updater.ProcessFile('libavformat/mp3dec.c')
72    updater.ProcessFile('libavformat/mp3enc.c')
73    updater.WriteCredits()
74
75    # Expect output to have just LGPL text (once) preceded by LICENSE.md
76    expected_lines = NormalizeNewLines(GetLicenseMdLines() +
77                                       GetSeparatorLines() +
78                                       GetLicenseLines(cu.License.LGPL))
79    credits_lines = ReadCreditsLines()
80    self.assertEqual(expected_lines, credits_lines)
81
82  def testKnownBucketFiles(self):
83    # Process some JPEG and MIPS files.
84    updater = NewCreditsUpdater()
85    updater.ProcessFile('libavcodec/jfdctfst.c')
86    updater.ProcessFile('libavutil/mips/float_dsp_mips.c')
87    updater.WriteCredits()
88
89    # Expected output to have JPEG and MIPS text in addition to the typical LGPL
90    # and LICENSE.md header. JPEG should appear before MIPS because known
91    # buckets will be printed in alphabetical order.
92    expected_lines = NormalizeNewLines(
93        GetLicenseMdLines() + GetSeparatorLines() +
94        ['libavcodec/jfdctfst.c\n\n'] + GetLicenseLines(cu.License.JPEG) +
95        GetSeparatorLines() + ['libavutil/mips/float_dsp_mips.c\n\n'] +
96        GetLicenseLines(cu.License.MIPS) + GetSeparatorLines() +
97        GetLicenseLines(cu.License.LGPL))
98    credits_lines = ReadCreditsLines()
99    self.assertEqual(expected_lines, credits_lines)
100
101  def testGeneratedAndKnownLicences(self):
102    # Process a file that doesn't fall into a known bucket (e.g. the license
103    # header for this file is unique). Also process a known bucket file.
104    updater = NewCreditsUpdater()
105    updater.ProcessFile('libswresample/swresample.h')
106    updater.ProcessFile('libavutil/mips/float_dsp_mips.c')
107    updater.WriteCredits()
108
109    # Expect output to put swresample.h header first, followed by MIPS.
110    expected_lines = NormalizeNewLines(
111        GetLicenseMdLines() + GetSeparatorLines() +
112        SWRESAMPLE_H_LICENSE.splitlines(True) + GetSeparatorLines() +
113        ['libavutil/mips/float_dsp_mips.c\n\n'] +
114        GetLicenseLines(cu.License.MIPS) + GetSeparatorLines() +
115        GetLicenseLines(cu.License.LGPL))
116    credits_lines = ReadCreditsLines()
117    self.assertEqual(expected_lines, credits_lines)
118
119  def testGeneratedLicencesOrder(self):
120    # Process files that do not fall into a known bucket and assert that their
121    # licenses are listed in alphabetical order of the file names.
122    files = [
123      'libswresample/swresample.h',
124      'libavcodec/arm/jrevdct_arm.S',
125      'libavcodec/mips/celp_math_mips.c',
126      'libavcodec/mips/acelp_vectors_mips.c',
127      'libavformat/oggparsetheora.c',
128      'libavcodec/x86/xvididct.asm',
129    ]
130    updater = NewCreditsUpdater()
131    for f in files:
132      updater.ProcessFile(f)
133    updater.WriteCredits()
134
135    credits = ''.join(ReadCreditsLines())
136    current_offset = 0
137    for f in sorted(files):
138      i = string.find(credits, f, current_offset)
139      if i == -1:
140        self.fail("Failed to find %s starting at offset %s of content:\n%s" %
141                  (f, current_offset, credits))
142      current_offset = i + len(f)
143
144
145  def testKnownFileDigestChange(self):
146    updater = NewCreditsUpdater()
147
148    # Choose a known file.
149    known_file = os.path.join('libavformat', 'oggparseogm.c')
150    self.assertTrue(known_file in updater.known_file_map)
151
152    # Show file processing works without raising SystemExit.
153    updater.ProcessFile(known_file)
154
155    # Alter the license digest for this file to simulate a change to the
156    # license header.
157    orig_file_info = updater.known_file_map[known_file]
158    altered_file_info = cu.FileInfo(cu.License.LGPL,
159                                    'chris' + orig_file_info.license_digest[5:])
160    updater.known_file_map[known_file] = altered_file_info
161
162    # Verify digest mismatch triggers SystemExit.
163    with self.assertRaises(SystemExit):
164      updater.ProcessFile(known_file)
165
166
167# Globals to cache the text of static files once read.
168g_license_md_lines = []
169g_license_lines = {}
170
171
172def ReadCreditsLines():
173  with open(os.path.join(SOURCE_DIR, OUTPUT_FILE)) as test_credits:
174    return test_credits.readlines()
175
176
177def GetLicenseMdLines():
178  global g_license_md_lines
179  if not len(g_license_md_lines):
180    with open(os.path.join(SOURCE_DIR, cu.UPSTREAM_LICENSEMD)) as license_md:
181      g_license_md_lines = license_md.readlines()
182  return g_license_md_lines
183
184
185def GetLicenseLines(license_file):
186  if not license_file in g_license_lines:
187    g_license_lines[license_file] = GetFileLines(
188        os.path.join(cu.LICENSE_TEXTS[license_file]))
189  return g_license_lines[license_file]
190
191
192def GetFileLines(file_path):
193  with open(file_path) as open_file:
194    return open_file.readlines()
195
196
197def GetSeparatorLines():
198  # Pass True to preserve \n chars in the return.
199  return cu.LICENSE_SEPARATOR.splitlines(True)
200
201
202# Combine into a string then split back out to a list. This is important for
203# making constructed expectations match the credits read from a file. E.g.
204# input: ['foo', '\n', 'bar']
205# return: ['foo\n', 'bar']
206# Comparing lists line by line makes for much better diffs when things go wrong.
207
208
209def NormalizeNewLines(lines):
210  return ''.join(lines).splitlines(True)
211
212
213if __name__ == '__main__':
214  unittest.main()
215