1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/shell_integration_linux.h"
6
7#include <algorithm>
8#include <cstdlib>
9#include <map>
10
11#include "base/base_paths.h"
12#include "base/environment.h"
13#include "base/file_util.h"
14#include "base/files/file_path.h"
15#include "base/files/scoped_temp_dir.h"
16#include "base/message_loop/message_loop.h"
17#include "base/stl_util.h"
18#include "base/strings/string_util.h"
19#include "base/strings/utf_string_conversions.h"
20#include "base/test/scoped_path_override.h"
21#include "chrome/browser/web_applications/web_app.h"
22#include "chrome/common/chrome_constants.h"
23#include "content/public/test/test_browser_thread.h"
24#include "testing/gmock/include/gmock/gmock.h"
25#include "testing/gtest/include/gtest/gtest.h"
26#include "url/gurl.h"
27
28#define FPL FILE_PATH_LITERAL
29
30using content::BrowserThread;
31using ::testing::ElementsAre;
32
33namespace {
34
35// Provides mock environment variables values based on a stored map.
36class MockEnvironment : public base::Environment {
37 public:
38  MockEnvironment() {}
39
40  void Set(const std::string& name, const std::string& value) {
41    variables_[name] = value;
42  }
43
44  virtual bool GetVar(const char* variable_name, std::string* result) OVERRIDE {
45    if (ContainsKey(variables_, variable_name)) {
46      *result = variables_[variable_name];
47      return true;
48    }
49
50    return false;
51  }
52
53  virtual bool SetVar(const char* variable_name,
54                      const std::string& new_value) OVERRIDE {
55    ADD_FAILURE();
56    return false;
57  }
58
59  virtual bool UnSetVar(const char* variable_name) OVERRIDE {
60    ADD_FAILURE();
61    return false;
62  }
63
64 private:
65  std::map<std::string, std::string> variables_;
66
67  DISALLOW_COPY_AND_ASSIGN(MockEnvironment);
68};
69
70}  // namespace
71
72TEST(ShellIntegrationTest, GetDataWriteLocation) {
73  base::MessageLoop message_loop;
74  content::TestBrowserThread file_thread(BrowserThread::FILE, &message_loop);
75
76  // Test that it returns $XDG_DATA_HOME.
77  {
78    MockEnvironment env;
79    env.Set("HOME", "/home/user");
80    env.Set("XDG_DATA_HOME", "/user/path");
81    base::FilePath path;
82    ASSERT_TRUE(ShellIntegrationLinux::GetDataWriteLocation(&env, &path));
83    EXPECT_EQ(base::FilePath("/user/path"), path);
84  }
85
86  // Test that $XDG_DATA_HOME falls back to $HOME/.local/share.
87  {
88    MockEnvironment env;
89    env.Set("HOME", "/home/user");
90    base::FilePath path;
91    ASSERT_TRUE(ShellIntegrationLinux::GetDataWriteLocation(&env, &path));
92    EXPECT_EQ(base::FilePath("/home/user/.local/share"), path);
93  }
94
95  // Test that if neither $XDG_DATA_HOME nor $HOME are specified, it fails.
96  {
97    MockEnvironment env;
98    base::FilePath path;
99    ASSERT_FALSE(ShellIntegrationLinux::GetDataWriteLocation(&env, &path));
100  }
101}
102
103TEST(ShellIntegrationTest, GetDataSearchLocations) {
104  base::MessageLoop message_loop;
105  content::TestBrowserThread file_thread(BrowserThread::FILE, &message_loop);
106
107  // Test that it returns $XDG_DATA_HOME + $XDG_DATA_DIRS.
108  {
109    MockEnvironment env;
110    env.Set("HOME", "/home/user");
111    env.Set("XDG_DATA_HOME", "/user/path");
112    env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
113    EXPECT_THAT(
114        ShellIntegrationLinux::GetDataSearchLocations(&env),
115        ElementsAre(base::FilePath("/user/path"),
116                    base::FilePath("/system/path/1"),
117                    base::FilePath("/system/path/2")));
118  }
119
120  // Test that $XDG_DATA_HOME falls back to $HOME/.local/share.
121  {
122    MockEnvironment env;
123    env.Set("HOME", "/home/user");
124    env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
125    EXPECT_THAT(
126        ShellIntegrationLinux::GetDataSearchLocations(&env),
127        ElementsAre(base::FilePath("/home/user/.local/share"),
128                    base::FilePath("/system/path/1"),
129                    base::FilePath("/system/path/2")));
130  }
131
132  // Test that if neither $XDG_DATA_HOME nor $HOME are specified, it still
133  // succeeds.
134  {
135    MockEnvironment env;
136    env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
137    EXPECT_THAT(
138        ShellIntegrationLinux::GetDataSearchLocations(&env),
139        ElementsAre(base::FilePath("/system/path/1"),
140                    base::FilePath("/system/path/2")));
141  }
142
143  // Test that $XDG_DATA_DIRS falls back to the two default paths.
144  {
145    MockEnvironment env;
146    env.Set("HOME", "/home/user");
147    env.Set("XDG_DATA_HOME", "/user/path");
148    EXPECT_THAT(
149        ShellIntegrationLinux::GetDataSearchLocations(&env),
150        ElementsAre(base::FilePath("/user/path"),
151                    base::FilePath("/usr/local/share"),
152                    base::FilePath("/usr/share")));
153  }
154}
155
156TEST(ShellIntegrationTest, GetExistingShortcutLocations) {
157  base::FilePath kProfilePath("Profile 1");
158  const char kExtensionId[] = "test_extension";
159  const char kTemplateFilename[] = "chrome-test_extension-Profile_1.desktop";
160  base::FilePath kTemplateFilepath(kTemplateFilename);
161  const char kNoDisplayDesktopFile[] = "[Desktop Entry]\nNoDisplay=true";
162
163  base::MessageLoop message_loop;
164  content::TestBrowserThread file_thread(BrowserThread::FILE, &message_loop);
165
166  // No existing shortcuts.
167  {
168    MockEnvironment env;
169    ShellIntegration::ShortcutLocations result =
170        ShellIntegrationLinux::GetExistingShortcutLocations(
171            &env, kProfilePath, kExtensionId);
172    EXPECT_FALSE(result.on_desktop);
173    EXPECT_EQ(ShellIntegration::APP_MENU_LOCATION_NONE,
174              result.applications_menu_location);
175
176    EXPECT_FALSE(result.in_quick_launch_bar);
177    EXPECT_FALSE(result.hidden);
178  }
179
180  // Shortcut on desktop.
181  {
182    base::ScopedTempDir temp_dir;
183    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
184    base::FilePath desktop_path = temp_dir.path();
185
186    MockEnvironment env;
187    ASSERT_TRUE(base::CreateDirectory(desktop_path));
188    ASSERT_FALSE(file_util::WriteFile(
189        desktop_path.AppendASCII(kTemplateFilename),
190        "", 0));
191    ShellIntegration::ShortcutLocations result =
192        ShellIntegrationLinux::GetExistingShortcutLocations(
193            &env, kProfilePath, kExtensionId, desktop_path);
194    EXPECT_TRUE(result.on_desktop);
195    EXPECT_EQ(ShellIntegration::APP_MENU_LOCATION_NONE,
196              result.applications_menu_location);
197
198    EXPECT_FALSE(result.in_quick_launch_bar);
199    EXPECT_FALSE(result.hidden);
200  }
201
202  // Shortcut in applications directory.
203  {
204    base::ScopedTempDir temp_dir;
205    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
206    base::FilePath apps_path = temp_dir.path().AppendASCII("applications");
207
208    MockEnvironment env;
209    env.Set("XDG_DATA_HOME", temp_dir.path().value());
210    ASSERT_TRUE(base::CreateDirectory(apps_path));
211    ASSERT_FALSE(file_util::WriteFile(
212        apps_path.AppendASCII(kTemplateFilename),
213        "", 0));
214    ShellIntegration::ShortcutLocations result =
215        ShellIntegrationLinux::GetExistingShortcutLocations(
216            &env, kProfilePath, kExtensionId);
217    EXPECT_FALSE(result.on_desktop);
218    EXPECT_EQ(ShellIntegration::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS,
219              result.applications_menu_location);
220
221    EXPECT_FALSE(result.in_quick_launch_bar);
222    EXPECT_FALSE(result.hidden);
223  }
224
225  // Shortcut in applications directory with NoDisplay=true.
226  {
227    base::ScopedTempDir temp_dir;
228    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
229    base::FilePath apps_path = temp_dir.path().AppendASCII("applications");
230
231    MockEnvironment env;
232    env.Set("XDG_DATA_HOME", temp_dir.path().value());
233    ASSERT_TRUE(base::CreateDirectory(apps_path));
234    ASSERT_TRUE(file_util::WriteFile(
235        apps_path.AppendASCII(kTemplateFilename),
236        kNoDisplayDesktopFile, strlen(kNoDisplayDesktopFile)));
237    ShellIntegration::ShortcutLocations result =
238        ShellIntegrationLinux::GetExistingShortcutLocations(
239            &env, kProfilePath, kExtensionId);
240    // Doesn't count as being in applications menu.
241    EXPECT_FALSE(result.on_desktop);
242    EXPECT_EQ(ShellIntegration::APP_MENU_LOCATION_NONE,
243              result.applications_menu_location);
244    EXPECT_FALSE(result.in_quick_launch_bar);
245    EXPECT_TRUE(result.hidden);
246  }
247
248  // Shortcut on desktop and in applications directory.
249  {
250    base::ScopedTempDir temp_dir1;
251    ASSERT_TRUE(temp_dir1.CreateUniqueTempDir());
252    base::FilePath desktop_path = temp_dir1.path();
253
254    base::ScopedTempDir temp_dir2;
255    ASSERT_TRUE(temp_dir2.CreateUniqueTempDir());
256    base::FilePath apps_path = temp_dir2.path().AppendASCII("applications");
257
258    MockEnvironment env;
259    ASSERT_TRUE(base::CreateDirectory(desktop_path));
260    ASSERT_FALSE(file_util::WriteFile(
261        desktop_path.AppendASCII(kTemplateFilename),
262        "", 0));
263    env.Set("XDG_DATA_HOME", temp_dir2.path().value());
264    ASSERT_TRUE(base::CreateDirectory(apps_path));
265    ASSERT_FALSE(file_util::WriteFile(
266        apps_path.AppendASCII(kTemplateFilename),
267        "", 0));
268    ShellIntegration::ShortcutLocations result =
269        ShellIntegrationLinux::GetExistingShortcutLocations(
270            &env, kProfilePath, kExtensionId, desktop_path);
271    EXPECT_TRUE(result.on_desktop);
272    EXPECT_EQ(ShellIntegration::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS,
273              result.applications_menu_location);
274    EXPECT_FALSE(result.in_quick_launch_bar);
275    EXPECT_FALSE(result.hidden);
276  }
277}
278
279TEST(ShellIntegrationTest, GetExistingShortcutContents) {
280  const char kTemplateFilename[] = "shortcut-test.desktop";
281  base::FilePath kTemplateFilepath(kTemplateFilename);
282  const char kTestData1[] = "a magical testing string";
283  const char kTestData2[] = "a different testing string";
284
285  base::MessageLoop message_loop;
286  content::TestBrowserThread file_thread(BrowserThread::FILE, &message_loop);
287
288  // Test that it searches $XDG_DATA_HOME/applications.
289  {
290    base::ScopedTempDir temp_dir;
291    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
292
293    MockEnvironment env;
294    env.Set("XDG_DATA_HOME", temp_dir.path().value());
295    // Create a file in a non-applications directory. This should be ignored.
296    ASSERT_TRUE(file_util::WriteFile(
297        temp_dir.path().AppendASCII(kTemplateFilename),
298        kTestData2, strlen(kTestData2)));
299    ASSERT_TRUE(base::CreateDirectory(
300        temp_dir.path().AppendASCII("applications")));
301    ASSERT_TRUE(file_util::WriteFile(
302        temp_dir.path().AppendASCII("applications")
303            .AppendASCII(kTemplateFilename),
304        kTestData1, strlen(kTestData1)));
305    std::string contents;
306    ASSERT_TRUE(
307        ShellIntegrationLinux::GetExistingShortcutContents(
308            &env, kTemplateFilepath, &contents));
309    EXPECT_EQ(kTestData1, contents);
310  }
311
312  // Test that it falls back to $HOME/.local/share/applications.
313  {
314    base::ScopedTempDir temp_dir;
315    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
316
317    MockEnvironment env;
318    env.Set("HOME", temp_dir.path().value());
319    ASSERT_TRUE(base::CreateDirectory(
320        temp_dir.path().AppendASCII(".local/share/applications")));
321    ASSERT_TRUE(file_util::WriteFile(
322        temp_dir.path().AppendASCII(".local/share/applications")
323            .AppendASCII(kTemplateFilename),
324        kTestData1, strlen(kTestData1)));
325    std::string contents;
326    ASSERT_TRUE(
327        ShellIntegrationLinux::GetExistingShortcutContents(
328            &env, kTemplateFilepath, &contents));
329    EXPECT_EQ(kTestData1, contents);
330  }
331
332  // Test that it searches $XDG_DATA_DIRS/applications.
333  {
334    base::ScopedTempDir temp_dir;
335    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
336
337    MockEnvironment env;
338    env.Set("XDG_DATA_DIRS", temp_dir.path().value());
339    ASSERT_TRUE(base::CreateDirectory(
340        temp_dir.path().AppendASCII("applications")));
341    ASSERT_TRUE(file_util::WriteFile(
342        temp_dir.path().AppendASCII("applications")
343            .AppendASCII(kTemplateFilename),
344        kTestData2, strlen(kTestData2)));
345    std::string contents;
346    ASSERT_TRUE(
347        ShellIntegrationLinux::GetExistingShortcutContents(
348            &env, kTemplateFilepath, &contents));
349    EXPECT_EQ(kTestData2, contents);
350  }
351
352  // Test that it searches $X/applications for each X in $XDG_DATA_DIRS.
353  {
354    base::ScopedTempDir temp_dir1;
355    ASSERT_TRUE(temp_dir1.CreateUniqueTempDir());
356    base::ScopedTempDir temp_dir2;
357    ASSERT_TRUE(temp_dir2.CreateUniqueTempDir());
358
359    MockEnvironment env;
360    env.Set("XDG_DATA_DIRS", temp_dir1.path().value() + ":" +
361                             temp_dir2.path().value());
362    // Create a file in a non-applications directory. This should be ignored.
363    ASSERT_TRUE(file_util::WriteFile(
364        temp_dir1.path().AppendASCII(kTemplateFilename),
365        kTestData1, strlen(kTestData1)));
366    // Only create a findable desktop file in the second path.
367    ASSERT_TRUE(base::CreateDirectory(
368        temp_dir2.path().AppendASCII("applications")));
369    ASSERT_TRUE(file_util::WriteFile(
370        temp_dir2.path().AppendASCII("applications")
371            .AppendASCII(kTemplateFilename),
372        kTestData2, strlen(kTestData2)));
373    std::string contents;
374    ASSERT_TRUE(
375        ShellIntegrationLinux::GetExistingShortcutContents(
376            &env, kTemplateFilepath, &contents));
377    EXPECT_EQ(kTestData2, contents);
378  }
379}
380
381TEST(ShellIntegrationTest, GetExtensionShortcutFilename) {
382  base::FilePath kProfilePath("a/b/c/Profile Name?");
383  const char kExtensionId[] = "extensionid";
384  EXPECT_EQ(base::FilePath("chrome-extensionid-Profile_Name_.desktop"),
385            ShellIntegrationLinux::GetExtensionShortcutFilename(
386                kProfilePath, kExtensionId));
387}
388
389TEST(ShellIntegrationTest, GetExistingProfileShortcutFilenames) {
390  base::FilePath kProfilePath("a/b/c/Profile Name?");
391  const char kApp1Filename[] = "chrome-extension1-Profile_Name_.desktop";
392  const char kApp2Filename[] = "chrome-extension2-Profile_Name_.desktop";
393  const char kUnrelatedAppFilename[] = "chrome-extension-Other_Profile.desktop";
394
395  base::MessageLoop message_loop;
396  content::TestBrowserThread file_thread(BrowserThread::FILE, &message_loop);
397
398  base::ScopedTempDir temp_dir;
399  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
400  ASSERT_EQ(0,
401            file_util::WriteFile(
402                temp_dir.path().AppendASCII(kApp1Filename), "", 0));
403  ASSERT_EQ(0,
404            file_util::WriteFile(
405                temp_dir.path().AppendASCII(kApp2Filename), "", 0));
406  // This file should not be returned in the results.
407  ASSERT_EQ(0,
408            file_util::WriteFile(
409                temp_dir.path().AppendASCII(kUnrelatedAppFilename), "", 0));
410  std::vector<base::FilePath> paths =
411      ShellIntegrationLinux::GetExistingProfileShortcutFilenames(
412          kProfilePath, temp_dir.path());
413  // Path order is arbitrary. Sort the output for consistency.
414  std::sort(paths.begin(), paths.end());
415  EXPECT_THAT(paths,
416              ElementsAre(base::FilePath(kApp1Filename),
417                          base::FilePath(kApp2Filename)));
418}
419
420TEST(ShellIntegrationTest, GetWebShortcutFilename) {
421  const struct {
422    const base::FilePath::CharType* path;
423    const char* url;
424  } test_cases[] = {
425    { FPL("http___foo_.desktop"), "http://foo" },
426    { FPL("http___foo_bar_.desktop"), "http://foo/bar/" },
427    { FPL("http___foo_bar_a=b&c=d.desktop"), "http://foo/bar?a=b&c=d" },
428
429    // Now we're starting to be more evil...
430    { FPL("http___foo_.desktop"), "http://foo/bar/baz/../../../../../" },
431    { FPL("http___foo_.desktop"), "http://foo/bar/././../baz/././../" },
432    { FPL("http___.._.desktop"), "http://../../../../" },
433  };
434  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) {
435    EXPECT_EQ(std::string(chrome::kBrowserProcessExecutableName) + "-" +
436              test_cases[i].path,
437              ShellIntegrationLinux::GetWebShortcutFilename(
438                  GURL(test_cases[i].url)).value()) <<
439        " while testing " << test_cases[i].url;
440  }
441}
442
443TEST(ShellIntegrationTest, GetDesktopFileContents) {
444  const base::FilePath kChromeExePath("/opt/google/chrome/google-chrome");
445  const struct {
446    const char* url;
447    const char* title;
448    const char* icon_name;
449    bool nodisplay;
450    const char* expected_output;
451  } test_cases[] = {
452    // Real-world case.
453    { "http://gmail.com",
454      "GMail",
455      "chrome-http__gmail.com",
456      false,
457
458      "#!/usr/bin/env xdg-open\n"
459      "[Desktop Entry]\n"
460      "Version=1.0\n"
461      "Terminal=false\n"
462      "Type=Application\n"
463      "Name=GMail\n"
464      "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
465      "Icon=chrome-http__gmail.com\n"
466      "StartupWMClass=gmail.com\n"
467    },
468
469    // Make sure that empty icons are replaced by the chrome icon.
470    { "http://gmail.com",
471      "GMail",
472      "",
473      false,
474
475      "#!/usr/bin/env xdg-open\n"
476      "[Desktop Entry]\n"
477      "Version=1.0\n"
478      "Terminal=false\n"
479      "Type=Application\n"
480      "Name=GMail\n"
481      "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
482      "Icon=chromium-browser\n"
483      "StartupWMClass=gmail.com\n"
484    },
485
486    // Test adding NoDisplay=true.
487    { "http://gmail.com",
488      "GMail",
489      "chrome-http__gmail.com",
490      true,
491
492      "#!/usr/bin/env xdg-open\n"
493      "[Desktop Entry]\n"
494      "Version=1.0\n"
495      "Terminal=false\n"
496      "Type=Application\n"
497      "Name=GMail\n"
498      "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
499      "Icon=chrome-http__gmail.com\n"
500      "NoDisplay=true\n"
501      "StartupWMClass=gmail.com\n"
502    },
503
504    // Now we're starting to be more evil...
505    { "http://evil.com/evil --join-the-b0tnet",
506      "Ownz0red\nExec=rm -rf /",
507      "chrome-http__evil.com_evil",
508      false,
509
510      "#!/usr/bin/env xdg-open\n"
511      "[Desktop Entry]\n"
512      "Version=1.0\n"
513      "Terminal=false\n"
514      "Type=Application\n"
515      "Name=http://evil.com/evil%20--join-the-b0tnet\n"
516      "Exec=/opt/google/chrome/google-chrome "
517      "--app=http://evil.com/evil%20--join-the-b0tnet\n"
518      "Icon=chrome-http__evil.com_evil\n"
519      "StartupWMClass=evil.com__evil%20--join-the-b0tnet\n"
520    },
521    { "http://evil.com/evil; rm -rf /; \"; rm -rf $HOME >ownz0red",
522      "Innocent Title",
523      "chrome-http__evil.com_evil",
524      false,
525
526      "#!/usr/bin/env xdg-open\n"
527      "[Desktop Entry]\n"
528      "Version=1.0\n"
529      "Terminal=false\n"
530      "Type=Application\n"
531      "Name=Innocent Title\n"
532      "Exec=/opt/google/chrome/google-chrome "
533      "\"--app=http://evil.com/evil;%20rm%20-rf%20/;%20%22;%20rm%20"
534      // Note: $ is escaped as \$ within an arg to Exec, and then
535      // the \ is escaped as \\ as all strings in a Desktop file should
536      // be; finally, \\ becomes \\\\ when represented in a C++ string!
537      "-rf%20\\\\$HOME%20%3Eownz0red\"\n"
538      "Icon=chrome-http__evil.com_evil\n"
539      "StartupWMClass=evil.com__evil;%20rm%20-rf%20_;%20%22;%20"
540      "rm%20-rf%20$HOME%20%3Eownz0red\n"
541    },
542    { "http://evil.com/evil | cat `echo ownz0red` >/dev/null",
543      "Innocent Title",
544      "chrome-http__evil.com_evil",
545      false,
546
547      "#!/usr/bin/env xdg-open\n"
548      "[Desktop Entry]\n"
549      "Version=1.0\n"
550      "Terminal=false\n"
551      "Type=Application\n"
552      "Name=Innocent Title\n"
553      "Exec=/opt/google/chrome/google-chrome "
554      "--app=http://evil.com/evil%20%7C%20cat%20%60echo%20ownz0red"
555      "%60%20%3E/dev/null\n"
556      "Icon=chrome-http__evil.com_evil\n"
557      "StartupWMClass=evil.com__evil%20%7C%20cat%20%60echo%20ownz0red"
558      "%60%20%3E_dev_null\n"
559    },
560  };
561
562  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) {
563    SCOPED_TRACE(i);
564    EXPECT_EQ(
565        test_cases[i].expected_output,
566        ShellIntegrationLinux::GetDesktopFileContents(
567            kChromeExePath,
568            web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)),
569            GURL(test_cases[i].url),
570            std::string(),
571            base::FilePath(),
572            ASCIIToUTF16(test_cases[i].title),
573            test_cases[i].icon_name,
574            base::FilePath(),
575            test_cases[i].nodisplay));
576  }
577}
578
579TEST(ShellIntegrationTest, GetDirectoryFileContents) {
580  const struct {
581    const char* title;
582    const char* icon_name;
583    const char* expected_output;
584  } test_cases[] = {
585    // Real-world case.
586    { "Chrome Apps",
587      "chrome-apps",
588
589      "[Desktop Entry]\n"
590      "Version=1.0\n"
591      "Type=Directory\n"
592      "Name=Chrome Apps\n"
593      "Icon=chrome-apps\n"
594    },
595
596    // Make sure that empty icons are replaced by the chrome icon.
597    { "Chrome Apps",
598      "",
599
600      "[Desktop Entry]\n"
601      "Version=1.0\n"
602      "Type=Directory\n"
603      "Name=Chrome Apps\n"
604      "Icon=chromium-browser\n"
605    },
606  };
607
608  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) {
609    SCOPED_TRACE(i);
610    EXPECT_EQ(
611        test_cases[i].expected_output,
612        ShellIntegrationLinux::GetDirectoryFileContents(
613            ASCIIToUTF16(test_cases[i].title),
614            test_cases[i].icon_name));
615  }
616}
617